on master help sign in
Add Entrypoint data type

It should parse "\cmd\"", "[\"cmd\"]", "[]" and "null" correctly. (and serialize to the respective json) Added tests for it as well as some autoformatting in tests.hs.

commit8b973346b7f6f15ab69f0c52aff54a6c845171b6 authorDeni Bertovic <deni@denibertovic.com> dateTue 21 Mar 2017, 10:13 (10 years ago) parent2cfedd0
3 files changed+150−83
docker.cabal+2 src/Docker/Client/Types.hs+24−6 tests/tests.hs+124−77
docker.cabal+2
@@ -27,6 +27,7 @@ library
2727 , data-default-class >= 0.0.1 && < 0.2.0
2828 , http-client >= 0.4.0 && < 0.5.0
2929 , http-types >= 0.9 && < 0.10.0
30+ , vector
3031 , conduit
3132 , conduit-combinators
3233 , conduit-extra
@@ -76,6 +77,7 @@ test-suite docker-hs-tests
7677 , transformers
7778 , QuickCheck
7879 , process
80+ , vector
7981 type: exitcode-stdio-1.0
8082 main-is: tests.hs
8183 hs-source-dirs: tests
src/Docker/Client/Types.hs+24−6
@@ -28,6 +28,7 @@ module Docker.Client.Types (
2828 , Label(..)
2929 , Tag
3030 , Image(..)
31+ , Entrypoint(..)
3132 , dropImagePrefix
3233 , CreateOpts(..)
3334 , BuildOpts(..)
@@ -96,13 +97,13 @@ import Data.Aeson (FromJSON, ToJSON, genericParseJSON,
9697import qualified Data.Aeson as JSON
9798import Data.Aeson.Types (defaultOptions, fieldLabelModifier)
9899import Data.Char (isAlphaNum, toUpper)
99−import Data.Foldable (asum)
100100import qualified Data.HashMap.Strict as HM
101101import Data.Monoid ((<>))
102102import Data.Scientific (floatingOrInteger)
103103import Data.Text (Text)
104104import qualified Data.Text as T
105105import Data.Time.Clock (UTCTime)
106+import qualified Data.Vector as V
106107import GHC.Generics (Generic)
107108import Prelude hiding (all, tail)
108109import Text.Read (readMaybe)
@@ -644,7 +645,7 @@ defaultContainerConfig imageName = ContainerConfig {
644645 , cmd=[]
645646 , volumes=[]
646647 , workingDir=Nothing
647− , entrypoint=[]
648+ , entrypoint=NoEntrypoint
648649 , networkDisabled=Nothing
649650 , macAddress=Nothing
650651 , labels=[]
@@ -1387,6 +1388,24 @@ instance {-# OVERLAPPING #-} ToJSON [ExposedPort] where
13871388 where key (ExposedPort p t) = show p <> slash <> show t
13881389 slash = T.unpack "/"
13891390
1391+data Entrypoint = Entrypoint [T.Text] | NoEntrypoint deriving (Eq, Show, Generic)
1392+
1393+instance ToJSON Entrypoint where
1394+ toJSON NoEntrypoint = JSON.Null
1395+ toJSON (Entrypoint (e:es)) = toJSON (e:es)
1396+ toJSON (Entrypoint []) = JSON.Null
1397+
1398+instance FromJSON Entrypoint where
1399+ parseJSON (JSON.String e) = return $ Entrypoint [e]
1400+ parseJSON (JSON.Array ar) = do
1401+ arr <- mapM parseJSON (V.toList ar)
1402+ case arr of
1403+ [] -> return NoEntrypoint
1404+ (e:es) -> return $ Entrypoint (e:es)
1405+ parseJSON JSON.Null = return NoEntrypoint
1406+ parseJSON _ = fail "Failed to parse Entrypoint"
1407+
1408+
13901409data ContainerConfig = ContainerConfig {
13911410 hostname :: Maybe Text
13921411 , domainname :: Maybe Text
@@ -1405,7 +1424,7 @@ data ContainerConfig = ContainerConfig {
14051424 , image :: Text
14061425 , volumes :: [Volume]
14071426 , workingDir :: Maybe FilePath
1408− , entrypoint :: [Text]
1427+ , entrypoint :: Entrypoint
14091428 , networkDisabled :: Maybe Bool -- Note: Should we expand the JSON instance and take away the Maybe? Null is False?
14101429 , macAddress :: Maybe Text
14111430 -- , onBuild :: Maybe Text -- For 1.24, only see this in the inspect response.
@@ -1413,6 +1432,7 @@ data ContainerConfig = ContainerConfig {
14131432 , stopSignal :: Signal
14141433 } deriving (Eq, Show, Generic)
14151434
1435+
14161436instance ToJSON ContainerConfig where
14171437 toJSON = genericToJSON defaultOptions {
14181438 fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}
@@ -1434,9 +1454,7 @@ instance FromJSON ContainerConfig where
14341454 image <- o .: "Image"
14351455 volumes <- o .: "Volumes"
14361456 workingDir <- o .:? "WorkingDir"
1437− entrypoint <- asum [ o .:? "Entrypoint" .!= []
1438− , (:[]) <$> o .: "Entrypoint"
1439− ]
1457+ entrypoint <- o .: "Entrypoint"
14401458 networkDisabled <- o .:? "networkDisabled"
14411459 macAddress <- o .:? "MacAddress"
14421460 labels <- o .:? "Labels" .!= []
tests/tests.hs+124−77
@@ -12,7 +12,8 @@ import Control.Lens ((^.), (^?))
1212import Control.Monad.IO.Class
1313import Control.Monad.Trans.Class (lift)
1414import qualified Data.Aeson as JSON
15−import Data.Aeson.Lens (key, _Object, _String, _Value)
15+import Data.Aeson.Lens (key, _Array, _Null, _Object,
16+ _String, _Value)
1617import qualified Data.ByteString as B
1718import qualified Data.ByteString.Char8 as C
1819import qualified Data.ByteString.Lazy as BL
@@ -22,6 +23,7 @@ import qualified Data.Map as M
2223import Data.Maybe (fromJust, isJust)
2324import Data.Monoid
2425import Data.Text (Text, unpack)
26+import qualified Data.Vector as V
2527import Network.Connection (TLSSettings (..))
2628import Network.HTTP.Client (newManager)
2729import Network.HTTP.Client.TLS
@@ -31,113 +33,158 @@ import System.Process (system)
3133import Docker.Client
3234
3335-- opts = defaultClientOpts
34−
3536testImageName = "docker-hs-test"
3637
3738toStrict1 = B.concat . BL.toChunks
3839
39−runDocker f = do
40− -- let opts = defaultClientOpts {baseUrl = "https://127.0.0.1:2376/"}
41− -- params' <- clientParamsWithClientAuthentication "127.0.0.1" 2376 "~/.docker/test-client-key.pem" "~/.docker/test-client-cert.pem" >>= fromRight
42− -- params <- clientParamsSetCA params' "~/.docker/test-ca.pem"
43− -- let settings = mkManagerSettings (TLSSettings params) Nothing
44− -- mgr <- newManager settings
45− h <- defaultHttpHandler
46− runDockerT (defaultClientOpts, h) f
40+runDocker f
41+ -- let opts = defaultClientOpts {baseUrl = "https://127.0.0.1:2376/"}
42+ -- params' <- clientParamsWithClientAuthentication "127.0.0.1" 2376 "~/.docker/test-client-key.pem" "~/.docker/test-client-cert.pem" >>= fromRight
43+ -- params <- clientParamsSetCA params' "~/.docker/test-ca.pem"
44+ -- let settings = mkManagerSettings (TLSSettings params) Nothing
45+ -- mgr <- newManager settings
46+ = do
47+ h <- defaultHttpHandler
48+ runDockerT (defaultClientOpts, h) f
4749
4850testDockerVersion :: IO ()
49−testDockerVersion = runDocker $ do
50− v <- getDockerVersion
51− lift $ assert $ isRight v
51+testDockerVersion =
52+ runDocker $
53+ do v <- getDockerVersion
54+ lift $ assert $ isRight v
5255
5356testFindImage :: IO ()
54−testFindImage = runDocker $ do
55− images <- listImages defaultListOpts >>= fromRight
56− let xs = filter ((== [imageFullName]) . imageRepoTags) images
57− lift $ assert $ length xs == 1
58− where imageFullName = testImageName <> ":latest"
57+testFindImage =
58+ runDocker $
59+ do images <- listImages defaultListOpts >>= fromRight
60+ let xs = filter ((== [imageFullName]) . imageRepoTags) images
61+ lift $ assert $ length xs == 1
62+ where
63+ imageFullName = testImageName <> ":latest"
5964
6065testRunAndReadLog :: IO ()
61−testRunAndReadLog = runDocker $ do
62− containerId <- createContainer (defaultCreateOpts (testImageName <> ":latest")) Nothing
63− c <- fromRight containerId
64− status1 <- startContainer defaultStartOpts c
65− _ <- inspectContainer c >>= fromRight
66− lift $ threadDelay 300000 -- give 300ms for the application to finish
67− lift $ assert $ isRightUnit status1
68− status2 <- killContainer SIGTERM c
69− logs <- getContainerLogs defaultLogOpts c >>= fromRight
70− lift $ assert $ isRightUnit status2
71− lift $ assert $ (C.pack "123") `C.isInfixOf` (toStrict1 logs)
72− status3 <- deleteContainer (DeleteOpts True True) c
73− lift $ assert $ isRightUnit status3
74−
75− where
76− isRightUnit (Right ()) = True
77− isRightUnit _ = False
66+testRunAndReadLog =
67+ runDocker $
68+ do containerId <- createContainer (defaultCreateOpts (testImageName <> ":latest")) Nothing
69+ c <- fromRight containerId
70+ status1 <- startContainer defaultStartOpts c
71+ _ <- inspectContainer c >>= fromRight
72+ lift $ threadDelay 300000 -- give 300ms for the application to finish
73+ lift $ assert $ isRightUnit status1
74+ status2 <- killContainer SIGTERM c
75+ logs <- getContainerLogs defaultLogOpts c >>= fromRight
76+ lift $ assert $ isRightUnit status2
77+ lift $ assert $ (C.pack "123") `C.isInfixOf` (toStrict1 logs)
78+ status3 <- deleteContainer (DeleteOpts True True) c
79+ lift $ assert $ isRightUnit status3
80+ where
81+ isRightUnit (Right ()) = True
82+ isRightUnit _ = False
7883
7984testLogDriverOptionsJson :: TestTree
80−testLogDriverOptionsJson = testGroup "Testing LogDriverOptions JSON" [ test1, test2, test3 ]
81− where
82− test1 = testCase "Driver option 1" $ assert $ JSON.toJSON sample ^. key key1 . _String == val1
83− test2 = testCase "Driver option 2" $ assert $ JSON.toJSON sample ^. key key2 . _String == val2
84− test3 = testCase "Test override" $ assert $ JSON.toJSON sample2 ^. key key1 . _String == "override"
85− sample = [LogDriverOption key1 val1, LogDriverOption key2 val2]
86− sample2 = [LogDriverOption key1 val1, LogDriverOption key1 "override"]
87− key1 = "some-key"
88− val1 = "some-val"
89− key2 = "some-key2"
90− val2 = "some-key2"
85+testLogDriverOptionsJson = testGroup "Testing LogDriverOptions JSON" [test1, test2, test3]
86+ where
87+ test1 = testCase "Driver option 1" $ assert $ JSON.toJSON sample ^. key key1 . _String == val1
88+ test2 = testCase "Driver option 2" $ assert $ JSON.toJSON sample ^. key key2 . _String == val2
89+ test3 =
90+ testCase "Test override" $ assert $ JSON.toJSON sample2 ^. key key1 . _String == "override"
91+ sample = [LogDriverOption key1 val1, LogDriverOption key2 val2]
92+ sample2 = [LogDriverOption key1 val1, LogDriverOption key1 "override"]
93+ key1 = "some-key"
94+ val1 = "some-val"
95+ key2 = "some-key2"
96+ val2 = "some-key2"
9197
9298testExposedPortsJson :: TestTree
93−testExposedPortsJson = testGroup "Testing ExposedPorts JSON" [ testTCP, testUDP ]
94− where
95− testTCP = testCase "tcp port" $ assert $ JSON.toJSON sampleEP ^. key "80/tcp" . _Object == HM.empty
96− testUDP = testCase "udp port" $ assert $ JSON.toJSON sampleEP ^. key "1337/tcp" . _Object == HM.empty
97− sampleEP = [ExposedPort 80 TCP, ExposedPort 1337 UDP]
99+testExposedPortsJson = testGroup "Testing ExposedPorts JSON" [testTCP, testUDP]
100+ where
101+ testTCP = testCase "tcp port" $ assert $ JSON.toJSON sampleEP ^. key "80/tcp" . _Object == HM.empty
102+ testUDP =
103+ testCase "udp port" $ assert $ JSON.toJSON sampleEP ^. key "1337/tcp" . _Object == HM.empty
104+ sampleEP = [ExposedPort 80 TCP, ExposedPort 1337 UDP]
98105
99106testLabelsJson :: TestTree
100−testLabelsJson = testGroup "Testing Labels JSON" [ testLS1, testLS2, testOverride ]
101− where
102− testLS1 = testCase "test label key1" $ assert $ JSON.toJSON sampleLS ^. key key1 . _String == val1
103− testLS2 = testCase "test label key2" $ assert $ JSON.toJSON sampleLS ^. key key2 . _String == val2
104− testOverride = testCase "test label override" $ assert $ JSON.toJSON [Label key1 val1, Label key1 "override"] ^. key key1 . _String == "override"
105− sampleLS = [Label key1 val1, Label key2 val2]
106− key1 = "com.example.some-label"
107− val1 = "some-value"
108− key2 = "something"
109− val2 = "value"
107+testLabelsJson = testGroup "Testing Labels JSON" [testLS1, testLS2, testOverride]
108+ where
109+ testLS1 = testCase "test label key1" $ assert $ JSON.toJSON sampleLS ^. key key1 . _String == val1
110+ testLS2 = testCase "test label key2" $ assert $ JSON.toJSON sampleLS ^. key key2 . _String == val2
111+ testOverride =
112+ testCase "test label override" $ assert $ JSON.toJSON [Label key1 val1, Label key1 "override"] ^.
113+ key key1 .
114+ _String ==
115+ "override"
116+ sampleLS = [Label key1 val1, Label key2 val2]
117+ key1 = "com.example.some-label"
118+ val1 = "some-value"
119+ key2 = "something"
120+ val2 = "value"
110121
111122testVolumesJson :: TestTree
112−testVolumesJson = testGroup "Testing Volumes JSON" [ testSample1, testSample2 ]
113− where
114− testSample1 = testCase "Test exposing volume: /tmp" $ assert $ JSON.toJSON sampleVolumes ^. key "/tmp" . _Object == HM.empty
115− testSample2 = testCase "Test exposing volume: /opt" $ assert $ JSON.toJSON sampleVolumes ^. key "/opt" . _Object == HM.empty
116− sampleVolumes = [Volume "/tmp", Volume "/opt"]
123+testVolumesJson = testGroup "Testing Volumes JSON" [testSample1, testSample2]
124+ where
125+ testSample1 =
126+ testCase "Test exposing volume: /tmp" $ assert $ JSON.toJSON sampleVolumes ^. key "/tmp" . _Object ==
127+ HM.empty
128+ testSample2 =
129+ testCase "Test exposing volume: /opt" $ assert $ JSON.toJSON sampleVolumes ^. key "/opt" . _Object ==
130+ HM.empty
131+ sampleVolumes = [Volume "/tmp", Volume "/opt"]
132+
133+testEntrypointJson :: TestTree
134+testEntrypointJson = testGroup "Testing ContainerConfig JSON" [testSample1, testSample2, testSample3, testSample4, testSample5]
135+ where
136+ testSample1 =
137+ testCase "Test toJSON with empty entrypoint (null)" $ assert $ JSON.toJSON NoEntrypoint ^. _Null == ()
138+ testSample2 =
139+ testCase "Test toJSON with entrypoint as Array" $ assert $
140+ JSON.toJSON (Entrypoint sampleEntrypointArr) ==
141+ JSON.toJSON sampleEntrypointArr
142+ testSample3 =
143+ testCase "Test decoding entrypoint as string" $ assert $ (JSON.decode "\"cmd\"") ==
144+ Just (Entrypoint ["cmd"])
145+ testSample4 =
146+ testCase "Test decoding as null" $ assert $ (JSON.decode "null" :: Maybe Entrypoint) ==
147+ Just NoEntrypoint
148+ testSample5 =
149+ testCase "Test decoding as array" $ assert $ (JSON.decode (JSON.encode sampleEntrypointArr) :: Maybe Entrypoint) ==
150+ Just (Entrypoint sampleEntrypointArr)
151+ sampleEntrypointArr = ["cmd", "--some-flag", "--some-flag2"]
117152
118153integrationTests :: TestTree
119−integrationTests = testGroup "Integration Tests" [
120− testCase "Get docker version" testDockerVersion,
121− testCase "Find image by name" testFindImage,
122− testCase "Run a dummy container and read its log" testRunAndReadLog]
154+integrationTests =
155+ testGroup
156+ "Integration Tests"
157+ [ testCase "Get docker version" testDockerVersion
158+ , testCase "Find image by name" testFindImage
159+ , testCase "Run a dummy container and read its log" testRunAndReadLog
160+ ]
123161
124162jsonTests :: TestTree
125−jsonTests = testGroup "JSON Tests" [testExposedPortsJson, testVolumesJson, testLabelsJson, testLogDriverOptionsJson]
163+jsonTests =
164+ testGroup
165+ "JSON Tests"
166+ [ testExposedPortsJson
167+ , testVolumesJson
168+ , testLabelsJson
169+ , testLogDriverOptionsJson
170+ , testEntrypointJson
171+ ]
126172
127173setup :: IO ()
128−setup = system ("docker build -t "++unpack testImageName++" tests") >> return ()
174+setup = system ("docker build -t " ++ unpack testImageName ++ " tests") >> return ()
129175
130−isRight (Left _) = False
176+isRight (Left _) = False
131177isRight (Right _) = True
132178
133−fromRight :: (MonadIO m, Show l) => Either l r -> m r
179+fromRight
180+ :: (MonadIO m, Show l)
181+ => Either l r -> m r
134182fromRight (Left l) = do
135− liftIO $ assertFailure $ "Left: " ++ show l
136− undefined
183+ liftIO $ assertFailure $ "Left: " ++ show l
184+ undefined
137185fromRight (Right r) = return r
138186
139187main :: IO ()
140188main = do
141189 setup
142190 defaultMain $ testGroup "Tests" [jsonTests, integrationTests]
143−