diff --git a/.gitignore b/.gitignore index 450f32e..7085620 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.swp dist dist-* cabal-dev diff --git a/README.md b/README.md index 4a6bf49..14162d0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # dockmaster command line utility providing conveniences around docker-compose + +### resources +[wiki on arg handling](https://wiki.haskell.org/Tutorials/Programming_Haskell/Argument_handling) +[bad ass opt package](https://github.com/pcapriotti/optparse-applicative) diff --git a/app/Main.hs b/app/Main.hs index de1c1ab..472d4cf 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -1,6 +1,12 @@ +{-# LANGUAGE OverloadedStrings #-} module Main where -import Lib +import Dockmaster.Types -main :: IO () -main = someFunc +import Data.Yaml +import qualified Data.ByteString as BS + +main = do + ymlData <- BS.readFile "dockmaster.yml" + let d = decodeEither ymlData :: Either String Dockmaster + print d diff --git a/dockmaster.cabal b/dockmaster.cabal index 9fa329e..d1f8da1 100644 --- a/dockmaster.cabal +++ b/dockmaster.cabal @@ -15,8 +15,11 @@ cabal-version: >=1.10 library hs-source-dirs: src - exposed-modules: Lib + exposed-modules: Dockmaster.Types build-depends: base >= 4.7 && < 5 + , yaml + , text + , unordered-containers default-language: Haskell2010 executable dockmaster-exe @@ -24,6 +27,8 @@ executable dockmaster-exe main-is: Main.hs ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base + , yaml + , bytestring , dockmaster default-language: Haskell2010 @@ -33,6 +38,9 @@ test-suite dockmaster-test main-is: Spec.hs build-depends: base , dockmaster + , directory + , yaml + , bytestring ghc-options: -threaded -rtsopts -with-rtsopts=-N default-language: Haskell2010 diff --git a/src/Dockmaster/Types.hs b/src/Dockmaster/Types.hs new file mode 100644 index 0000000..4e25ed3 --- /dev/null +++ b/src/Dockmaster/Types.hs @@ -0,0 +1,65 @@ +{-# LANGUAGE OverloadedStrings #-} +module Dockmaster.Types where + +import Data.Yaml +import Control.Applicative +import Data.HashMap.Lazy (HashMap, lookup, member) +import Prelude hiding (lookup) +import qualified Data.Text as T + +-- | Dockmaster configuration (specified by dockmaster.yml) +data Dockmaster = Dockmaster { dmFile :: Maybe ComposeFile + , dmTargets :: [Target] + , dmCommands :: [HashMap T.Text CommandConfig] + } deriving (Show) + +-- | Configuration for docker-compose.yml file template & vars +data ComposeFile = ComposeFile { cfPath :: Maybe String + , cfConfig :: [String] + } deriving (Show) + +-- | Targets are used to identify where compositions are run +data Target = Target { targetName :: Maybe String + , targetType :: Maybe String + , targetMachine :: Maybe String + } deriving (Show) + +-- | Hooks can be specified by filename or direct shell command +data Hook = File String | Shell String deriving (Show) + +-- | Configuration for each command +data CommandConfig = CommandConfig { ccCompose :: Bool + , ccPreHooks :: [Hook] + , ccPostHooks :: [Hook] + } deriving (Show) + +instance FromJSON ComposeFile where + parseJSON (Object v) = ComposeFile + <$> v .:? "path" + <*> v .:? "config" .!= [] + +instance FromJSON Target where + parseJSON (Object v) = Target + <$> v .:? "name" + <*> v .:? "type" + <*> v .:? "machine" + +instance FromJSON Hook where + parseJSON (Object v) + | member "file" v = File <$> v .: "file" + | member "shell" v = Shell <$> v .: "shell" + | otherwise = error "Hooks are specified by file or shell only" + +instance FromJSON CommandConfig where + parseJSON (Object v) = CommandConfig + <$> v .:? "compose" .!= True + <*> v .:? "pre_hooks" .!= [] + <*> v .:? "post_hooks" .!= [] + +instance FromJSON Dockmaster where + parseJSON (Object v) = Dockmaster + <$> v .:? "file" + <*> v .:? "targets" .!= [] + <*> v .:? "commands" .!= [] + -- A non-Object value is of the wrong type, so fail. + parseJSON _ = error "Can't parse Dockmaster from YAML/JSON" diff --git a/src/Lib.hs b/src/Lib.hs deleted file mode 100644 index d36ff27..0000000 --- a/src/Lib.hs +++ /dev/null @@ -1,6 +0,0 @@ -module Lib - ( someFunc - ) where - -someFunc :: IO () -someFunc = putStrLn "someFunc" diff --git a/test/Spec.hs b/test/Spec.hs index cd4753f..9cdc887 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1,2 +1,25 @@ +module Main where + +import Data.Yaml +import Dockmaster.Types +import qualified Data.ByteString as BS +import Data.Maybe +import System.Exit +import System.Directory + +parseDockmasterYml :: FilePath -> IO Bool +parseDockmasterYml "." = return True +parseDockmasterYml ".." = return True +parseDockmasterYml file = do + contents <- BS.readFile $ "./test/fixtures/" ++ file + case (decodeEither contents :: Either String Dockmaster) of + (Left e) -> putStrLn ("Failed to parse " ++ file ++":") >> putStrLn e >> return False + otherwise -> putStrLn ("Parsed " ++ file ++ " successfully.") >> return True + main :: IO () -main = putStrLn "Test suite not yet implemented" +main = do + files <- getDirectoryContents "./test/fixtures/" + results <- mapM parseDockmasterYml files + if and results + then exitWith ExitSuccess + else exitWith (ExitFailure 1) diff --git a/test/fixtures/dockmaster.yml b/test/fixtures/dockmaster.yml new file mode 100644 index 0000000..87bbeea --- /dev/null +++ b/test/fixtures/dockmaster.yml @@ -0,0 +1,24 @@ +file: + path: docker-compose.j2 + config: + - /etc/dockmaster/defaults.yml + - env.vars + +targets: + - name: node-a + type: docker-machine + + # optional, defaults to name + machine: node-a + +commands: + - up: + pre_hooks: + - file: relative_path/to/hook.sh + - file: /absolute/path/to/hook.sh + - shell: rm -rf .working + - wiggle: + compose: false + # ^^^ does not call docker-compose between pre- and post- hooks. + pre_hooks: + - file: wiggle.sh