module Propellor.Property (
requires
, before
, onChange
, onChangeFlagOnFail
, flagFile
, flagFile'
, check
, fallback
, revert
, describe
, (==>)
, Propellor
, property
, property'
, OuterMetaTypesWitness
, ensureProperty
, pickOS
, withOS
, unsupportedOS
, unsupportedOS'
, makeChange
, noChange
, doNothing
, endAction
, UncheckedProperty
, unchecked
, changesFile
, changesFileContent
, isNewerThan
, checkResult
, Checkable
, assume
) where
import System.FilePath
import Control.Monad
import Data.Monoid
import Control.Monad.IfElse
import "mtl" Control.Monad.RWS.Strict
import System.Posix.Files
import Data.Maybe
import Data.List
import Data.Hashable
import Control.Applicative
import Prelude
import Propellor.Types
import Propellor.Types.Core
import Propellor.Types.ResultCheck
import Propellor.Types.MetaTypes
import Propellor.Types.Singletons
import Propellor.Info
import Propellor.EnsureProperty
import Utility.Exception
import Utility.Monad
import Utility.Directory
import Utility.Misc
flagFile :: Property i -> FilePath -> Property i
flagFile p = flagFile' p . return
flagFile' :: Property i -> IO FilePath -> Property i
flagFile' p getflagfile = adjustPropertySatisfy p $ \satisfy -> do
flagfile <- liftIO getflagfile
go satisfy flagfile =<< liftIO (doesFileExist flagfile)
where
go _ _ True = return NoChange
go satisfy flagfile False = do
r <- satisfy
when (r == MadeChange) $ liftIO $
unlessM (doesFileExist flagfile) $ do
createDirectoryIfMissing True (takeDirectory flagfile)
writeFile flagfile ""
return r
requires :: Combines x y => x -> y -> CombinedType x y
requires = combineWith
(flip (<>))
(<>)
before :: Combines x y => x -> y -> CombinedType x y
before = combineWith
(<>)
(flip (<>))
onChange
:: (Combines x y)
=> x
-> y
-> CombinedType x y
onChange = combineWith combiner revertcombiner
where
combiner (Just p) (Just hook) = Just $ do
r <- p
case r of
MadeChange -> do
r' <- hook
return $ r <> r'
_ -> return r
combiner (Just p) Nothing = Just p
combiner Nothing _ = Nothing
revertcombiner = (<>)
onChangeFlagOnFail
:: (Combines x y)
=> FilePath
-> x
-> y
-> CombinedType x y
onChangeFlagOnFail flagfile = combineWith combiner revertcombiner
where
combiner (Just s1) s2 = Just $ do
r1 <- s1
case r1 of
MadeChange -> flagFailed s2
_ -> ifM (liftIO $ doesFileExist flagfile)
( flagFailed s2
, return r1
)
combiner Nothing _ = Nothing
revertcombiner = (<>)
flagFailed (Just s) = do
r <- s
liftIO $ case r of
FailedChange -> createFlagFile
_ -> removeFlagFile
return r
flagFailed Nothing = return NoChange
createFlagFile = unlessM (doesFileExist flagfile) $ do
createDirectoryIfMissing True (takeDirectory flagfile)
writeFile flagfile ""
removeFlagFile = whenM (doesFileExist flagfile) $ removeFile flagfile
describe :: IsProp p => p -> Desc -> p
describe = setDesc
(==>) :: IsProp (Property i) => Desc -> Property i -> Property i
(==>) = flip describe
infixl 1 ==>
fallback :: (Combines p1 p2) => p1 -> p2 -> CombinedType p1 p2
fallback = combineWith combiner revertcombiner
where
combiner (Just a1) (Just a2) = Just $ do
r <- a1
if r == FailedChange
then a2
else return r
combiner (Just a1) Nothing = Just a1
combiner Nothing _ = Nothing
revertcombiner = (<>)
changesFile :: Checkable p i => p i -> FilePath -> Property i
changesFile p f = checkResult getstat comparestat p
where
getstat = catchMaybeIO $ getSymbolicLinkStatus f
comparestat oldstat = do
newstat <- getstat
return $ if samestat oldstat newstat then NoChange else MadeChange
samestat Nothing Nothing = True
samestat (Just a) (Just b) = and
[ deviceID a == deviceID b
, fileID a == fileID b
, fileMode a == fileMode b
, fileOwner a == fileOwner b
, fileGroup a == fileGroup b
, specialDeviceID a == specialDeviceID b
, fileSize a == fileSize b
, modificationTimeHiRes a == modificationTimeHiRes b
, isBlockDevice a == isBlockDevice b
, isCharacterDevice a == isCharacterDevice b
, isNamedPipe a == isNamedPipe b
, isRegularFile a == isRegularFile b
, isDirectory a == isDirectory b
, isSymbolicLink a == isSymbolicLink b
, isSocket a == isSocket b
]
samestat _ _ = False
changesFileContent :: Checkable p i => p i -> FilePath -> Property i
changesFileContent p f = checkResult gethash comparehash p
where
gethash = catchMaybeIO $ hash <$> readFileStrict f
comparehash oldhash = do
newhash <- gethash
return $ if oldhash == newhash then NoChange else MadeChange
isNewerThan :: FilePath -> FilePath -> IO Bool
isNewerThan x y = do
mx <- mtime x
my <- mtime y
return (mx > my)
where
mtime f = catchMaybeIO $ modificationTimeHiRes <$> getFileStatus f
pickOS
::
( SingKind ('KProxy :: KProxy ka)
, SingKind ('KProxy :: KProxy kb)
, DemoteRep ('KProxy :: KProxy ka) ~ [MetaType]
, DemoteRep ('KProxy :: KProxy kb) ~ [MetaType]
, SingI c
)
=> Property (MetaTypes (a :: ka))
-> Property (MetaTypes (b :: kb))
-> Property (MetaTypes c)
pickOS a b = c `addChildren` [toChildProperty a, toChildProperty b]
where
c = withOS (getDesc a) $ \_ o ->
if matching o a
then maybe (pure NoChange) id (getSatisfy a)
else if matching o b
then maybe (pure NoChange) id (getSatisfy b)
else unsupportedOS'
matching Nothing _ = False
matching (Just o) p =
Targeting (systemToTargetOS o)
`elem`
fromSing (proptype p)
proptype (Property t _ _ _ _) = t
withOS
:: (SingI metatypes)
=> Desc
-> (OuterMetaTypesWitness '[]-> Maybe System -> Propellor Result)
-> Property (MetaTypes metatypes)
withOS desc a = property desc $ a dummyoutermetatypes =<< getOS
where
dummyoutermetatypes :: OuterMetaTypesWitness ('[])
dummyoutermetatypes = OuterMetaTypesWitness sing
unsupportedOS :: Property UnixLike
unsupportedOS = property "unsupportedOS" unsupportedOS'
unsupportedOS' :: Propellor Result
unsupportedOS' = go =<< getOS
where
go Nothing = error "Unknown host OS is not supported by this property."
go (Just o) = error $ "This property is not implemented for " ++ show o
revert :: RevertableProperty setup undo -> RevertableProperty undo setup
revert (RevertableProperty p1 p2) = RevertableProperty p2 p1
makeChange :: IO () -> Propellor Result
makeChange a = liftIO a >> return MadeChange
noChange :: Propellor Result
noChange = return NoChange
doNothing :: SingI t => Property (MetaTypes t)
doNothing = mempty
endAction :: Desc -> (Result -> Propellor Result) -> Propellor ()
endAction desc a = tell [EndAction desc a]