    {-# LANGUAGE PackageImports #-}

    -- | This module lets you construct Properties by running commands and
    -- scripts. To get from an `UncheckedProperty` to a `Property`, it's
    -- up to the user to check if the command made a change to the system.
    --
    -- The best approach is to `check` a property, so that the command is only
    -- run when it needs to be. With this method, you avoid running the
    -- `cmdProperty` unnecessarily.
    --
    -- > check (not <$> userExists "bob")
    -- > 	(cmdProperty "useradd" ["bob"])
    --
    -- Sometimes it's just as expensive to check a property as it would be to
    -- run the command that ensures the property. So you can let the command
    -- run every time, and use `changesFile` or `checkResult` to determine if
    -- anything changed:
    --
    -- > cmdProperty "chmod" ["600", "/etc/secret"]
    -- > 	`changesFile` "/etc/secret"
    --
    -- Or you can punt and `assume` a change was made, but then propellor will
    -- always say it make a change, and `onChange` will always fire.
    --
    -- > cmdProperty "service" ["foo", "reload"]
    -- > 	`assume` MadeChange

    module Propellor.Property.Cmd (
    	-- * Constricting properties running commands and scripts
    	cmdProperty,
    	cmdProperty',
    	cmdPropertyEnv,
    	Script,
    	scriptProperty,
    	userScriptProperty,
    	cmdResult,
    	-- * Lower-level interface for running commands
    	CommandParam(..),
    	boolSystem,
    	boolSystemEnv,
    	safeSystem,
    	safeSystemEnv,
    	shellEscape,
    	createProcess,
    	waitForProcess,
    ) where

    import Data.List
    import "mtl" Control.Monad.Reader
    import Control.Applicative
    import Prelude

    import Propellor.Types
    import Propellor.Property
    import Utility.SafeCommand
    import Utility.Env
    import Utility.Process (createProcess, CreateProcess, waitForProcess)

    -- | A property that can be satisfied by running a command.
    --
    -- The command must exit 0 on success.
    cmdProperty :: String -> [String] -> UncheckedProperty UnixLike
    cmdProperty :: String -> [String] -> UncheckedProperty UnixLike
    cmdProperty cmd :: String
    cmd params :: [String]
    params = String
    -> [String]
    -> (CreateProcess -> CreateProcess)
    -> UncheckedProperty UnixLike
    cmdProperty' String
    cmd [String]
    params CreateProcess -> CreateProcess
    forall a. a -> a
    id

    cmdProperty' :: String -> [String] -> (CreateProcess -> CreateProcess) -> UncheckedProperty UnixLike
    cmdProperty' :: String
    -> [String]
    -> (CreateProcess -> CreateProcess)
    -> UncheckedProperty UnixLike
    cmdProperty' cmd :: String
    cmd params :: [String]
    params mkprocess :: CreateProcess -> CreateProcess
    mkprocess = Property UnixLike -> UncheckedProperty UnixLike
    forall i. Property i -> UncheckedProperty i
    unchecked (Property UnixLike -> UncheckedProperty UnixLike)
    -> Property UnixLike -> UncheckedProperty UnixLike
    forall a b. (a -> b) -> a -> b
    $ String -> Propellor Result -> Property UnixLike
    forall k (metatypes :: k).
    SingI metatypes =>
    String -> Propellor Result -> Property (MetaTypes metatypes)
    property String
    desc (Propellor Result -> Property UnixLike)
    -> Propellor Result -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ IO Result -> Propellor Result
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO Result -> Propellor Result) -> IO Result -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $
    	Bool -> Result
    cmdResult (Bool -> Result) -> IO Bool -> IO Result
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> String
    -> [CommandParam] -> (CreateProcess -> CreateProcess) -> IO Bool
    boolSystem' String
    cmd ((String -> CommandParam) -> [String] -> [CommandParam]
    forall a b. (a -> b) -> [a] -> [b]
    map String -> CommandParam
    Param [String]
    params) CreateProcess -> CreateProcess
    mkprocess
      where
    	desc :: String
    desc = [String] -> String
    unwords ([String] -> String) -> [String] -> String
    forall a b. (a -> b) -> a -> b
    $ String
    cmd String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : [String]
    params

    cmdResult :: Bool -> Result
    cmdResult :: Bool -> Result
    cmdResult False = Result
    FailedChange
    cmdResult True = Result
    NoChange

    -- | A property that can be satisfied by running a command,
    -- with added environment variables in addition to the standard
    -- environment.
    cmdPropertyEnv :: String -> [String] -> [(String, String)] -> UncheckedProperty UnixLike
    cmdPropertyEnv :: String
    -> [String] -> [(String, String)] -> UncheckedProperty UnixLike
    cmdPropertyEnv cmd :: String
    cmd params :: [String]
    params env :: [(String, String)]
    env = Property UnixLike -> UncheckedProperty UnixLike
    forall i. Property i -> UncheckedProperty i
    unchecked (Property UnixLike -> UncheckedProperty UnixLike)
    -> Property UnixLike -> UncheckedProperty UnixLike
    forall a b. (a -> b) -> a -> b
    $ String -> Propellor Result -> Property UnixLike
    forall k (metatypes :: k).
    SingI metatypes =>
    String -> Propellor Result -> Property (MetaTypes metatypes)
    property String
    desc (Propellor Result -> Property UnixLike)
    -> Propellor Result -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ IO Result -> Propellor Result
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO Result -> Propellor Result) -> IO Result -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $ do
    	[(String, String)]
    env' <- [(String, String)] -> [(String, String)] -> [(String, String)]
    forall k v. Eq k => [(k, v)] -> [(k, v)] -> [(k, v)]
    addEntries [(String, String)]
    env ([(String, String)] -> [(String, String)])
    -> IO [(String, String)] -> IO [(String, String)]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> IO [(String, String)]
    getEnvironment
    	Bool -> Result
    cmdResult (Bool -> Result) -> IO Bool -> IO Result
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> String -> [CommandParam] -> Maybe [(String, String)] -> IO Bool
    boolSystemEnv String
    cmd ((String -> CommandParam) -> [String] -> [CommandParam]
    forall a b. (a -> b) -> [a] -> [b]
    map String -> CommandParam
    Param [String]
    params) ([(String, String)] -> Maybe [(String, String)]
    forall a. a -> Maybe a
    Just [(String, String)]
    env')
      where
    	desc :: String
    desc = [String] -> String
    unwords ([String] -> String) -> [String] -> String
    forall a b. (a -> b) -> a -> b
    $ String
    cmd String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : [String]
    params

    -- | A series of shell commands. (Without a leading hashbang.)
    type Script = [String]

    -- | A property that can be satisfied by running a script.
    scriptProperty :: Script -> UncheckedProperty UnixLike
    scriptProperty :: [String] -> UncheckedProperty UnixLike
    scriptProperty script :: [String]
    script = String -> [String] -> UncheckedProperty UnixLike
    cmdProperty "sh" ["-c", String
    shellcmd]
      where
    	shellcmd :: String
    shellcmd = String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate " ; " ("set -e" String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : [String]
    script)

    -- | A property that can satisfied by running a script
    -- as user (cd'd to their home directory).
    userScriptProperty :: User -> Script -> UncheckedProperty UnixLike
    userScriptProperty :: User -> [String] -> UncheckedProperty UnixLike
    userScriptProperty (User user :: String
    user) script :: [String]
    script = String -> [String] -> UncheckedProperty UnixLike
    cmdProperty "su"
    	["--login", "--shell", "/bin/sh", "-c", String
    shellcmd, String
    user]
      where
    	shellcmd :: String
    shellcmd = String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate " ; " ("set -e" String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : "cd" String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : [String]
    script)