    -- | Maintainer: 2016 Evan Cofsky <evan@theunixman.com>
    --
    -- FreeBSD Poudriere properties

    {-# Language GeneralizedNewtypeDeriving, DeriveDataTypeable #-}

    module Propellor.Property.FreeBSD.Poudriere where

    import Propellor.Base
    import Propellor.Types.Info
    import qualified Propellor.Property.FreeBSD.Pkg as Pkg
    import qualified Propellor.Property.ZFS as ZFS
    import qualified Propellor.Property.File as File

    import Data.List
    import qualified Data.Semigroup as Sem

    poudriereConfigPath :: FilePath
    poudriereConfigPath :: FilePath
    poudriereConfigPath = "/usr/local/etc/poudriere.conf"

    newtype PoudriereConfigured = PoudriereConfigured String
    	deriving (Typeable, b -> PoudriereConfigured -> PoudriereConfigured
    NonEmpty PoudriereConfigured -> PoudriereConfigured
    PoudriereConfigured -> PoudriereConfigured -> PoudriereConfigured
    (PoudriereConfigured -> PoudriereConfigured -> PoudriereConfigured)
    -> (NonEmpty PoudriereConfigured -> PoudriereConfigured)
    -> (forall b.
        Integral b =>
        b -> PoudriereConfigured -> PoudriereConfigured)
    -> Semigroup PoudriereConfigured
    forall b.
    Integral b =>
    b -> PoudriereConfigured -> PoudriereConfigured
    forall a.
    (a -> a -> a)
    -> (NonEmpty a -> a)
    -> (forall b. Integral b => b -> a -> a)
    -> Semigroup a
    stimes :: b -> PoudriereConfigured -> PoudriereConfigured
    $cstimes :: forall b.
    Integral b =>
    b -> PoudriereConfigured -> PoudriereConfigured
    sconcat :: NonEmpty PoudriereConfigured -> PoudriereConfigured
    $csconcat :: NonEmpty PoudriereConfigured -> PoudriereConfigured
    <> :: PoudriereConfigured -> PoudriereConfigured -> PoudriereConfigured
    $c<> :: PoudriereConfigured -> PoudriereConfigured -> PoudriereConfigured
    Sem.Semigroup, Semigroup PoudriereConfigured
    PoudriereConfigured
    Semigroup PoudriereConfigured =>
    PoudriereConfigured
    -> (PoudriereConfigured
        -> PoudriereConfigured -> PoudriereConfigured)
    -> ([PoudriereConfigured] -> PoudriereConfigured)
    -> Monoid PoudriereConfigured
    [PoudriereConfigured] -> PoudriereConfigured
    PoudriereConfigured -> PoudriereConfigured -> PoudriereConfigured
    forall a.
    Semigroup a =>
    a -> (a -> a -> a) -> ([a] -> a) -> Monoid a
    mconcat :: [PoudriereConfigured] -> PoudriereConfigured
    $cmconcat :: [PoudriereConfigured] -> PoudriereConfigured
    mappend :: PoudriereConfigured -> PoudriereConfigured -> PoudriereConfigured
    $cmappend :: PoudriereConfigured -> PoudriereConfigured -> PoudriereConfigured
    mempty :: PoudriereConfigured
    $cmempty :: PoudriereConfigured
    $cp1Monoid :: Semigroup PoudriereConfigured
    Monoid, Int -> PoudriereConfigured -> ShowS
    [PoudriereConfigured] -> ShowS
    PoudriereConfigured -> FilePath
    (Int -> PoudriereConfigured -> ShowS)
    -> (PoudriereConfigured -> FilePath)
    -> ([PoudriereConfigured] -> ShowS)
    -> Show PoudriereConfigured
    forall a.
    (Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
    showList :: [PoudriereConfigured] -> ShowS
    $cshowList :: [PoudriereConfigured] -> ShowS
    show :: PoudriereConfigured -> FilePath
    $cshow :: PoudriereConfigured -> FilePath
    showsPrec :: Int -> PoudriereConfigured -> ShowS
    $cshowsPrec :: Int -> PoudriereConfigured -> ShowS
    Show)

    instance IsInfo PoudriereConfigured where
    	propagateInfo :: PoudriereConfigured -> PropagateInfo
    propagateInfo _ = Bool -> PropagateInfo
    PropagateInfo Bool
    False

    poudriereConfigured :: PoudriereConfigured -> Bool
    poudriereConfigured :: PoudriereConfigured -> Bool
    poudriereConfigured (PoudriereConfigured _) = Bool
    True

    setConfigured :: Property (HasInfo + FreeBSD)
    setConfigured :: Property (HasInfo + FreeBSD)
    setConfigured = Property
      (MetaTypes
         '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
            'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
    -> Property (HasInfo + FreeBSD)
    forall (p :: * -> *) (untightened :: [MetaType])
           (tightened :: [MetaType]).
    (TightenTargets p, TightenTargetsAllowed untightened tightened,
     SingI tightened) =>
    p (MetaTypes untightened) -> p (MetaTypes tightened)
    tightenTargets (Property
       (MetaTypes
          '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
             'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
     -> Property (HasInfo + FreeBSD))
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
               'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
    -> Property (HasInfo + FreeBSD)
    forall a b. (a -> b) -> a -> b
    $
    	FilePath -> PoudriereConfigured -> Property (HasInfo + UnixLike)
    forall v.
    IsInfo v =>
    FilePath -> v -> Property (HasInfo + UnixLike)
    pureInfoProperty "Poudriere Configured" (FilePath -> PoudriereConfigured
    PoudriereConfigured "")

    poudriere :: Poudriere -> Property (HasInfo + FreeBSD)
    poudriere :: Poudriere -> Property (HasInfo + FreeBSD)
    poudriere conf :: Poudriere
    conf@(Poudriere _ _ _ _ _ _ zfs :: Maybe PoudriereZFS
    zfs) = Property FreeBSD
    prop
    	Property FreeBSD
    -> Property FreeBSD
    -> CombinedType (Property FreeBSD) (Property FreeBSD)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` FilePath -> Property FreeBSD
    Pkg.installed "poudriere"
    	Property FreeBSD
    -> Property (MetaTypes '[ 'WithInfo, 'Targeting 'OSFreeBSD])
    -> CombinedType
         (Property FreeBSD)
         (Property (MetaTypes '[ 'WithInfo, 'Targeting 'OSFreeBSD]))
    forall x y. Combines x y => x -> y -> CombinedType x y
    `before` Property (MetaTypes '[ 'WithInfo, 'Targeting 'OSFreeBSD])
    Property (HasInfo + FreeBSD)
    setConfigured
      where
    	confProp :: Property FreeBSD
    	confProp :: Property FreeBSD
    confProp = Property UnixLike -> Property FreeBSD
    forall (p :: * -> *) (untightened :: [MetaType])
           (tightened :: [MetaType]).
    (TightenTargets p, TightenTargetsAllowed untightened tightened,
     SingI tightened) =>
    p (MetaTypes untightened) -> p (MetaTypes tightened)
    tightenTargets (Property UnixLike -> Property FreeBSD)
    -> Property UnixLike -> Property FreeBSD
    forall a b. (a -> b) -> a -> b
    $
    		FilePath -> [FilePath] -> Property UnixLike
    File.containsLines FilePath
    poudriereConfigPath (Poudriere -> [FilePath]
    forall a. ToShellConfigLines a => a -> [FilePath]
    toLines Poudriere
    conf)
    	setZfs :: PoudriereZFS -> Property UnixLike
    setZfs (PoudriereZFS z :: ZFS
    z p :: ZFSProperties
    p) = ZFS -> ZFSProperties -> Property ZFSOS
    ZFS.zfsSetProperties ZFS
    z ZFSProperties
    p Property UnixLike -> FilePath -> Property UnixLike
    forall p. IsProp p => p -> FilePath -> p
    `describe` "Configuring Poudriere with ZFS"
    	prop :: Property FreeBSD
    	prop :: Property FreeBSD
    prop
    		| Maybe PoudriereZFS -> Bool
    forall a. Maybe a -> Bool
    isJust Maybe PoudriereZFS
    zfs = ((PoudriereZFS -> Property UnixLike
    setZfs (PoudriereZFS -> Property UnixLike)
    -> PoudriereZFS -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ Maybe PoudriereZFS -> PoudriereZFS
    forall a. HasCallStack => Maybe a -> a
    fromJust Maybe PoudriereZFS
    zfs) Property UnixLike
    -> Property FreeBSD
    -> CombinedType (Property UnixLike) (Property FreeBSD)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `before` Property FreeBSD
    confProp)
    		| Bool
    otherwise = Property FreeBSD
    confProp Property FreeBSD -> FilePath -> Property FreeBSD
    forall p. IsProp p => p -> FilePath -> p
    `describe` "Configuring Poudriere without ZFS"

    poudriereCommand :: String -> [String] -> (String, [String])
    poudriereCommand :: FilePath -> [FilePath] -> (FilePath, [FilePath])
    poudriereCommand cmd :: FilePath
    cmd args :: [FilePath]
    args = ("poudriere", FilePath
    cmdFilePath -> [FilePath] -> [FilePath]
    forall a. a -> [a] -> [a]
    :[FilePath]
    args)

    runPoudriere :: String -> [String] -> IO [String]
    runPoudriere :: FilePath -> [FilePath] -> IO [FilePath]
    runPoudriere cmd :: FilePath
    cmd args :: [FilePath]
    args =
    	let
    		(p :: FilePath
    p, a :: [FilePath]
    a) = FilePath -> [FilePath] -> (FilePath, [FilePath])
    poudriereCommand FilePath
    cmd [FilePath]
    args
    	in
    		FilePath -> [FilePath]
    lines (FilePath -> [FilePath]) -> IO FilePath -> IO [FilePath]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> [FilePath] -> IO FilePath
    readProcess FilePath
    p [FilePath]
    a

    listJails :: IO [String]
    listJails :: IO [FilePath]
    listJails = (FilePath -> Maybe FilePath) -> [FilePath] -> [FilePath]
    forall a b. (a -> Maybe b) -> [a] -> [b]
    mapMaybe ([FilePath] -> Maybe FilePath
    forall a. [a] -> Maybe a
    headMaybe ([FilePath] -> Maybe FilePath)
    -> (FilePath -> [FilePath]) -> FilePath -> Maybe FilePath
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Int -> [FilePath] -> [FilePath]
    forall a. Int -> [a] -> [a]
    take 1 ([FilePath] -> [FilePath])
    -> (FilePath -> [FilePath]) -> FilePath -> [FilePath]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> [FilePath]
    words)
    	([FilePath] -> [FilePath]) -> IO [FilePath] -> IO [FilePath]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> [FilePath] -> IO [FilePath]
    runPoudriere "jail" ["-l", "-q"]

    jailExists :: Jail -> IO Bool
    jailExists :: Jail -> IO Bool
    jailExists (Jail name :: FilePath
    name _ _) = [FilePath] -> [FilePath] -> Bool
    forall a. Eq a => [a] -> [a] -> Bool
    isInfixOf [FilePath
    name] ([FilePath] -> Bool) -> IO [FilePath] -> IO Bool
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> IO [FilePath]
    listJails

    jail :: Jail -> Property FreeBSD
    jail :: Jail -> Property FreeBSD
    jail j :: Jail
    j@(Jail name :: FilePath
    name version :: FBSDVersion
    version arch :: PoudriereArch
    arch) = Property UnixLike -> Property FreeBSD
    forall (p :: * -> *) (untightened :: [MetaType])
           (tightened :: [MetaType]).
    (TightenTargets p, TightenTargetsAllowed untightened tightened,
     SingI tightened) =>
    p (MetaTypes untightened) -> p (MetaTypes tightened)
    tightenTargets (Property UnixLike -> Property FreeBSD)
    -> Property UnixLike -> Property FreeBSD
    forall a b. (a -> b) -> a -> b
    $
    	let
    		chk :: Propellor Bool
    chk = do
    			Bool
    c <- PoudriereConfigured -> Bool
    poudriereConfigured (PoudriereConfigured -> Bool)
    -> Propellor PoudriereConfigured -> Propellor Bool
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Propellor PoudriereConfigured
    forall v. IsInfo v => Propellor v
    askInfo
    			Bool
    nx <- IO Bool -> Propellor Bool
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO Bool -> Propellor Bool) -> IO Bool -> Propellor Bool
    forall a b. (a -> b) -> a -> b
    $ Bool -> Bool
    not (Bool -> Bool) -> IO Bool -> IO Bool
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Jail -> IO Bool
    jailExists Jail
    j
    			Bool -> Propellor Bool
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Bool -> Propellor Bool) -> Bool -> Propellor Bool
    forall a b. (a -> b) -> a -> b
    $ Bool
    c Bool -> Bool -> Bool
    && Bool
    nx

    		(cmd :: FilePath
    cmd, args :: [FilePath]
    args) = FilePath -> [FilePath] -> (FilePath, [FilePath])
    poudriereCommand "jail"  ["-c", "-j", FilePath
    name, "-a", PoudriereArch -> FilePath
    forall t. ConfigurableValue t => t -> FilePath
    val PoudriereArch
    arch, "-v", FBSDVersion -> FilePath
    forall t. ConfigurableValue t => t -> FilePath
    val FBSDVersion
    version]
    		createJail :: UncheckedProperty UnixLike
    createJail = FilePath -> [FilePath] -> UncheckedProperty UnixLike
    cmdProperty FilePath
    cmd [FilePath]
    args
    	in
    		Propellor Bool -> UncheckedProperty UnixLike -> Property UnixLike
    forall (p :: * -> *) i (m :: * -> *).
    (Checkable p i, LiftPropellor m) =>
    m Bool -> p i -> Property i
    check Propellor Bool
    chk UncheckedProperty UnixLike
    createJail
    			Property UnixLike -> FilePath -> Property UnixLike
    forall p. IsProp p => p -> FilePath -> p
    `describe` [FilePath] -> FilePath
    unwords ["Create poudriere jail", FilePath
    name]

    data JailInfo = JailInfo String

    data Poudriere = Poudriere
    	{ Poudriere -> FilePath
    _resolvConf :: String
    	, Poudriere -> FilePath
    _freebsdHost :: String
    	, Poudriere -> FilePath
    _baseFs :: String
    	, Poudriere -> Bool
    _usePortLint :: Bool
    	, Poudriere -> FilePath
    _distFilesCache :: FilePath
    	, Poudriere -> FilePath
    _svnHost :: String
    	, Poudriere -> Maybe PoudriereZFS
    _zfs :: Maybe PoudriereZFS
    	}

    defaultConfig :: Poudriere
    defaultConfig :: Poudriere
    defaultConfig = FilePath
    -> FilePath
    -> FilePath
    -> Bool
    -> FilePath
    -> FilePath
    -> Maybe PoudriereZFS
    -> Poudriere
    Poudriere
    	"/etc/resolv.conf"
    	"ftp://ftp5.us.FreeBSD.org"
    	"/usr/local/poudriere"
    	Bool
    True
    	"/usr/ports/distfiles"
    	"svn.freebsd.org"
    	Maybe PoudriereZFS
    forall a. Maybe a
    Nothing

    data PoudriereZFS = PoudriereZFS ZFS.ZFS ZFS.ZFSProperties

    data Jail = Jail String FBSDVersion PoudriereArch

    data PoudriereArch = I386 | AMD64 deriving (PoudriereArch -> PoudriereArch -> Bool
    (PoudriereArch -> PoudriereArch -> Bool)
    -> (PoudriereArch -> PoudriereArch -> Bool) -> Eq PoudriereArch
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    /= :: PoudriereArch -> PoudriereArch -> Bool
    $c/= :: PoudriereArch -> PoudriereArch -> Bool
    == :: PoudriereArch -> PoudriereArch -> Bool
    $c== :: PoudriereArch -> PoudriereArch -> Bool
    Eq)

    instance ConfigurableValue PoudriereArch where
    	val :: PoudriereArch -> FilePath
    val I386 = "i386"
    	val AMD64 = "amd64"

    fromArchitecture :: Architecture -> PoudriereArch
    fromArchitecture :: Architecture -> PoudriereArch
    fromArchitecture X86_64 = PoudriereArch
    AMD64
    fromArchitecture X86_32 = PoudriereArch
    I386
    fromArchitecture _ = FilePath -> PoudriereArch
    forall a. HasCallStack => FilePath -> a
    error "Not a valid Poudriere architecture."

    yesNoProp :: Bool -> String
    yesNoProp :: Bool -> FilePath
    yesNoProp b :: Bool
    b = if Bool
    b then "yes" else "no"

    instance ToShellConfigLines Poudriere where
    	toAssoc :: Poudriere -> [(FilePath, FilePath)]
    toAssoc c :: Poudriere
    c = ((FilePath, Poudriere -> FilePath) -> (FilePath, FilePath))
    -> [(FilePath, Poudriere -> FilePath)] -> [(FilePath, FilePath)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\(k :: FilePath
    k, f :: Poudriere -> FilePath
    f) -> (FilePath
    k, Poudriere -> FilePath
    f Poudriere
    c))
    		[ ("RESOLV_CONF", Poudriere -> FilePath
    _resolvConf)
    		, ("FREEBSD_HOST", Poudriere -> FilePath
    _freebsdHost)
    		, ("BASEFS", Poudriere -> FilePath
    _baseFs)
    		, ("USE_PORTLINT", Bool -> FilePath
    yesNoProp (Bool -> FilePath) -> (Poudriere -> Bool) -> Poudriere -> FilePath
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Poudriere -> Bool
    _usePortLint)
    		, ("DISTFILES_CACHE", Poudriere -> FilePath
    _distFilesCache)
    		, ("SVN_HOST", Poudriere -> FilePath
    _svnHost)
    		] [(FilePath, FilePath)]
    -> [(FilePath, FilePath)] -> [(FilePath, FilePath)]
    forall a. [a] -> [a] -> [a]
    ++ [(FilePath, FilePath)]
    -> (PoudriereZFS -> [(FilePath, FilePath)])
    -> Maybe PoudriereZFS
    -> [(FilePath, FilePath)]
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe [ ("NO_ZFS", "yes") ] PoudriereZFS -> [(FilePath, FilePath)]
    forall a. ToShellConfigLines a => a -> [(FilePath, FilePath)]
    toAssoc (Poudriere -> Maybe PoudriereZFS
    _zfs Poudriere
    c)

    instance ToShellConfigLines PoudriereZFS where
    	toAssoc :: PoudriereZFS -> [(FilePath, FilePath)]
    toAssoc (PoudriereZFS (ZFS.ZFS (ZFS.ZPool pool :: FilePath
    pool) dataset :: ZDataset
    dataset) _) =
    		[ ("NO_ZFS", "no")
    		, ("ZPOOL", FilePath
    pool)
    		, ("ZROOTFS", ZDataset -> FilePath
    forall t. ConfigurableValue t => t -> FilePath
    val ZDataset
    dataset)
    		]

    type ConfigLine = String
    type ConfigFile = [ConfigLine]

    class ToShellConfigLines a where
    	toAssoc :: a -> [(String, String)]

    	toLines :: a -> [ConfigLine]
    	toLines c :: a
    c = ((FilePath, FilePath) -> FilePath)
    -> [(FilePath, FilePath)] -> [FilePath]
    forall a b. (a -> b) -> [a] -> [b]
    map (\(k :: FilePath
    k, v :: FilePath
    v) -> FilePath -> [FilePath] -> FilePath
    forall a. [a] -> [[a]] -> [a]
    intercalate "=" [FilePath
    k, FilePath
    v]) (a -> [(FilePath, FilePath)]
    forall a. ToShellConfigLines a => a -> [(FilePath, FilePath)]
    toAssoc a
    c)

    confFile :: FilePath
    confFile :: FilePath
    confFile = "/usr/local/etc/poudriere.conf"