From 277adba731d66e642bc8f5bdfd37c3590ec40ec4 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sat, 5 Dec 2015 19:25:52 -0400 Subject: make Locale.selectedFor check contents of file to decide if it needs to make a change --- src/Propellor/Property/Locale.hs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/Propellor/Property/Locale.hs b/src/Propellor/Property/Locale.hs index 0342a2f2..15babf20 100644 --- a/src/Propellor/Property/Locale.hs +++ b/src/Propellor/Property/Locale.hs @@ -24,14 +24,19 @@ type LocaleVariable = String selectedFor :: Locale -> [LocaleVariable] -> RevertableProperty NoInfo locale `selectedFor` vars = select deselect where - select = cmdProperty "update-locale" selectArgs - `assume` NoChange + select = check (not <$> isselected) select' `requires` available locale `describe` (locale ++ " locale selected") - deselect = cmdProperty "update-locale" vars - `assume` NoChange + select' = cmdProperty "update-locale" selectArgs + `assume` MadeChange + deselect = check isselected deselect' `describe` (locale ++ " locale deselected") + deselect' = cmdProperty "update-locale" vars + `assume` MadeChange selectArgs = zipWith (++) vars (repeat ('=':locale)) + isselected = do + ls <- catchDefaultIO [] $ lines <$> readFile "/etc/default/locale" + return $ and $ map (\v -> v ++ "=" ++ locale `elem` ls) vars -- | Ensures a locale is generated (or, if reverted, ensure it's not). -- -- cgit v1.3-2-g0d8e From d2406996c7d71f310be3d390d531812e3d0d4521 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sat, 5 Dec 2015 19:29:15 -0400 Subject: refactor --- src/Propellor/Property/Locale.hs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/Propellor/Property/Locale.hs b/src/Propellor/Property/Locale.hs index 15babf20..29de8df2 100644 --- a/src/Propellor/Property/Locale.hs +++ b/src/Propellor/Property/Locale.hs @@ -34,9 +34,13 @@ locale `selectedFor` vars = select deselect deselect' = cmdProperty "update-locale" vars `assume` MadeChange selectArgs = zipWith (++) vars (repeat ('=':locale)) - isselected = do - ls <- catchDefaultIO [] $ lines <$> readFile "/etc/default/locale" - return $ and $ map (\v -> v ++ "=" ++ locale `elem` ls) vars + isselected = locale `isSelectedFor` vars + +isSelectedFor :: Locale -> [LocaleVariable] -> IO Bool +locale `isSelectedFor` vars = do + ls <- catchDefaultIO [] $ lines <$> readFile "/etc/default/locale" + return $ and $ map (\v -> v ++ "=" ++ locale `elem` ls) vars + -- | Ensures a locale is generated (or, if reverted, ensure it's not). -- -- cgit v1.3-2-g0d8e From 54f086238c4fb20490e8369c99c910991b0d5be8 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 00:05:39 -0400 Subject: Apt.cacheCleaned was trivial, force NoChange --- src/Propellor/Property/Apt.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/Propellor/Property/Apt.hs b/src/Propellor/Property/Apt.hs index 26c151d9..f25d8ee7 100644 --- a/src/Propellor/Property/Apt.hs +++ b/src/Propellor/Property/Apt.hs @@ -304,7 +304,7 @@ aptKeyFile k = "/etc/apt/trusted.gpg.d" keyname k ++ ".gpg" -- space. cacheCleaned :: Property NoInfo cacheCleaned = cmdProperty "apt-get" ["clean"] - `assume` MadeChange + `assume` NoChange `describe` "apt cache cleaned" -- | Add a foreign architecture to dpkg and apt. -- cgit v1.3-2-g0d8e From 8093dd640eb2c1dac952984d76f08cef0f81edea Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 00:09:08 -0400 Subject: check for changes to sasldb2 --- src/Propellor/Property/Postfix.hs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/Propellor/Property/Postfix.hs b/src/Propellor/Property/Postfix.hs index bc46ac21..91a02927 100644 --- a/src/Propellor/Property/Postfix.hs +++ b/src/Propellor/Property/Postfix.hs @@ -170,15 +170,17 @@ saslAuthdInstalled = setupdaemon -- -- The password is taken from the privdata. saslPasswdSet :: Domain -> User -> Property HasInfo -saslPasswdSet domain (User user) = withPrivData src ctx $ \getpw -> trivial $ - property ("sasl password for " ++ uatd) $ getpw $ \pw -> makeChange $ - withHandle StdinHandle createProcessSuccess p $ \h -> do - hPutStrLn h (privDataVal pw) - hClose h +saslPasswdSet domain (User user) = go `changesFile` "/etc/sasldb2" where + go = withPrivData src ctx $ \getpw -> + property desc $ getpw $ \pw -> liftIO $ + withHandle StdinHandle createProcessSuccess p $ \h -> do + hPutStrLn h (privDataVal pw) + hClose h + return NoChange + desc = "sasl password for " ++ uatd uatd = user ++ "@" ++ domain ps = ["-p", "-c", "-u", domain, user] p = proc "saslpasswd2" ps ctx = Context "sasl" src = PrivDataSource (Password uatd) "enter password" - trivial = flip assume NoChange -- cgit v1.3-2-g0d8e From 35b91efc665b5f20f6d82046dfce818be669c8a3 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 00:21:32 -0400 Subject: setting the same sasl password updates the mtime of the file, but the contents remain the same Don't much like using Data.Hash.MD5, but it's available in dependencies and pulling in a real hash library would be overkill. And md5 is a perfectly ok hash to use here. --- src/Propellor/Property.hs | 20 ++++++++++++++++++-- src/Propellor/Property/Postfix.hs | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/Propellor/Property.hs b/src/Propellor/Property.hs index 2976acf1..c58cc9fe 100644 --- a/src/Propellor/Property.hs +++ b/src/Propellor/Property.hs @@ -28,6 +28,7 @@ module Propellor.Property ( , UncheckedProperty , unchecked , changesFile + , changesFileContent , checkResult , Checkable , assume @@ -36,10 +37,12 @@ module Propellor.Property ( import System.Directory import System.FilePath import Control.Monad +import Control.Applicative import Data.Monoid import Control.Monad.IfElse import "mtl" Control.Monad.RWS.Strict import System.Posix.Files +import qualified Data.Hash.MD5 as MD5 import Propellor.Types import Propellor.Types.ResultCheck @@ -47,6 +50,7 @@ import Propellor.Info import Propellor.Exception import Utility.Exception import Utility.Monad +import Utility.Misc -- | Constructs a Property, from a description and an action to run to -- ensure the Property is met. @@ -185,11 +189,12 @@ fallback = combineWith combiner revertcombiner revertcombiner = (<>) -- | Indicates that a Property may change a particular file. When the file --- is modified, the property will return MadeChange instead of NoChange. +-- is modified in any way (including changing its permissions or mtime), +-- the property will return MadeChange instead of NoChange. changesFile :: Checkable p i => p i -> FilePath -> Property i changesFile p f = checkResult getstat comparestat p where - getstat = liftIO $ catchMaybeIO $ getSymbolicLinkStatus f + getstat = catchMaybeIO $ getSymbolicLinkStatus f comparestat oldstat = do newstat <- getstat return $ if samestat oldstat newstat then NoChange else MadeChange @@ -214,6 +219,17 @@ changesFile p f = checkResult getstat comparestat p ] samestat _ _ = False +-- | Like `changesFile`, but compares the content of the file. +-- Changes to mtime etc that do not change file content are treated as +-- NoChange. +changesFileContent :: Checkable p i => p i -> FilePath -> Property i +changesFileContent p f = checkResult getmd5 comparemd5 p + where + getmd5 = catchMaybeIO $ MD5.md5 . MD5.Str <$> readFileStrictAnyEncoding f + comparemd5 oldmd5 = do + newmd5 <- getmd5 + return $ if oldmd5 == newmd5 then NoChange else MadeChange + -- | Makes a property that is satisfied differently depending on the host's -- operating system. -- diff --git a/src/Propellor/Property/Postfix.hs b/src/Propellor/Property/Postfix.hs index 91a02927..bcb9fb30 100644 --- a/src/Propellor/Property/Postfix.hs +++ b/src/Propellor/Property/Postfix.hs @@ -170,7 +170,7 @@ saslAuthdInstalled = setupdaemon -- -- The password is taken from the privdata. saslPasswdSet :: Domain -> User -> Property HasInfo -saslPasswdSet domain (User user) = go `changesFile` "/etc/sasldb2" +saslPasswdSet domain (User user) = go `changesFileContent` "/etc/sasldb2" where go = withPrivData src ctx $ \getpw -> property desc $ getpw $ \pw -> liftIO $ -- cgit v1.3-2-g0d8e From 8bd4bf6751a21af59062e066824c3501825d0184 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 00:29:57 -0400 Subject: improve error display --- src/Propellor/Property/Journald.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/Propellor/Property/Journald.hs b/src/Propellor/Property/Journald.hs index 6c8bda80..2fbb780e 100644 --- a/src/Propellor/Property/Journald.hs +++ b/src/Propellor/Property/Journald.hs @@ -17,7 +17,8 @@ type DataSize = String configuredSize :: Systemd.Option -> DataSize -> Property NoInfo configuredSize option s = case readSize dataUnits s of Just sz -> configured option (systemdSizeUnits sz) - Nothing -> property ("unable to parse " ++ option ++ " data size " ++ s) noChange + Nothing -> property ("unable to parse " ++ option ++ " data size " ++ s) $ + return FailedChange systemMaxUse :: DataSize -> Property NoInfo systemMaxUse = configuredSize "SystemMaxUse" -- cgit v1.3-2-g0d8e From d5710d64c15b57c8a5d37437d4a36c3eb231d4f4 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 00:30:07 -0400 Subject: avoid removing line we wanted to add, only to add it back later --- src/Propellor/Property/Systemd.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/Propellor/Property/Systemd.hs b/src/Propellor/Property/Systemd.hs index 04ce3b48..5a08fb1e 100644 --- a/src/Propellor/Property/Systemd.hs +++ b/src/Propellor/Property/Systemd.hs @@ -158,7 +158,7 @@ configured cfgfile option value = combineProperties desc line = setting ++ value desc = cfgfile ++ " " ++ line removeother l - | setting `isPrefixOf` l = Nothing + | setting `isPrefixOf` l && l /= line = Nothing | otherwise = Just l -- | Causes systemd to reload its configuration files. -- cgit v1.3-2-g0d8e From fe746525f3efe483d7a50575a325faa88089112c Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 00:51:25 -0400 Subject: only write /etc/hosts once, avoiding always returning MadeChange --- src/Propellor/Property/Hostname.hs | 42 ++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/Propellor/Property/Hostname.hs b/src/Propellor/Property/Hostname.hs index fcb88f59..7ab350ae 100644 --- a/src/Propellor/Property/Hostname.hs +++ b/src/Propellor/Property/Hostname.hs @@ -35,31 +35,33 @@ setTo :: HostName -> Property NoInfo setTo = setTo' extractDomain setTo' :: ExtractDomain -> HostName -> Property NoInfo -setTo' extractdomain hn = combineProperties desc go +setTo' extractdomain hn = combineProperties desc + [ "/etc/hostname" `File.hasContent` [basehost] + , hostslines $ catMaybes + [ if null domain + then Nothing + else Just ("127.0.1.1", [hn, basehost]) + , Just ("127.0.0.1", ["localhost"]) + ] + , check (not <$> inChroot) $ + cmdProperty "hostname" [basehost] + `assume` NoChange + , "/etc/mailname" `File.hasContent` + [if null domain then hn else domain] + ] where desc = "hostname " ++ hn basehost = takeWhile (/= '.') hn domain = extractdomain hn - - go = catMaybes - [ Just $ "/etc/hostname" `File.hasContent` [basehost] - , if null domain - then Nothing - else Just $ hostsline "127.0.1.1" [hn, basehost] - , Just $ hostsline "127.0.0.1" ["localhost"] - , Just $ check (not <$> inChroot) $ - cmdProperty "hostname" [basehost] - `assume` NoChange - , Just $ "/etc/mailname" `File.hasContent` - [if null domain then hn else domain] - ] - hostsline ip names = File.fileProperty desc - (addhostsline ip names) - "/etc/hosts" - addhostsline ip names ls = - (ip ++ "\t" ++ (unwords names)) : filter (not . hasip ip) ls - hasip ip l = headMaybe (words l) == Just ip + hostslines ipsnames = + File.fileProperty desc (addhostslines ipsnames) "/etc/hosts" + addhostslines :: [(String, [String])] -> [String] -> [String] + addhostslines ipsnames ls = + let ips = map fst ipsnames + hasip l = maybe False (`elem` ips) (headMaybe (words l)) + mkline (ip, names) = ip ++ "\t" ++ (unwords names) + in map mkline ipsnames ++ filter (not . hasip) ls -- | Makes contain search and domain lines for -- the domain that the hostname is in. -- cgit v1.3-2-g0d8e From a94a3ba26e0c0676bd8beb03b3309a36fd393fde Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 01:04:41 -0400 Subject: avoid running update-grub except for just after installing the grub package --- src/Propellor/Property/Grub.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/Propellor/Property/Grub.hs b/src/Propellor/Property/Grub.hs index 024a2827..1b7f2a0a 100644 --- a/src/Propellor/Property/Grub.hs +++ b/src/Propellor/Property/Grub.hs @@ -20,11 +20,10 @@ data BIOS = PC | EFI64 | EFI32 | Coreboot | Xen -- -- This includes running update-grub. installed :: BIOS -> Property NoInfo -installed bios = installed' bios `before` mkConfig +installed bios = installed' bios `onChange` mkConfig -- Run update-grub, to generate the grub boot menu. It will be --- automatically updated when kernel packages are --- -- installed. +-- automatically updated when kernel packages are installed. mkConfig :: Property NoInfo mkConfig = cmdProperty "update-grub" [] `assume` MadeChange -- cgit v1.3-2-g0d8e From f404f5ed9a79449c620fde5bd669ab41fcb8d0fb Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 13:30:00 -0400 Subject: improve docs --- src/Propellor/Property/Cmd.hs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/Propellor/Property/Cmd.hs b/src/Propellor/Property/Cmd.hs index b02376a3..3db00bc1 100644 --- a/src/Propellor/Property/Cmd.hs +++ b/src/Propellor/Property/Cmd.hs @@ -1,7 +1,33 @@ {-# 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, and you know that whenever it runs, a +-- change was made. +-- +-- > check (not <$> userExists "bob") +-- > (cmdProperty "useradd" ["bob"] `assume` MadeChange) +-- +-- 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 ( - -- * Properties for running commands and scripts + -- * Constricting properties running commands and scripts cmdProperty, cmdProperty', cmdPropertyEnv, @@ -32,11 +58,6 @@ import Utility.Process (createProcess, CreateProcess, waitForProcess) -- | A property that can be satisfied by running a command. -- -- The command must exit 0 on success. --- --- This and other properties in this module are `UncheckedProperty`, --- and return `NoChange`. It's up to the user to check if the command --- made a change to the system, perhaps by using `checkResult` or --- `changesFile`, or you can use @cmdProperty "foo" ["bar"] `assume` MadeChange@ cmdProperty :: String -> [String] -> UncheckedProperty NoInfo cmdProperty cmd params = cmdProperty' cmd params id -- cgit v1.3-2-g0d8e From 94f91a44810dc3a1eca95c843e3c444cbbe87006 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 13:30:50 -0400 Subject: add isNewerThan and use it to avoid unnecessary running of newaliases --- src/Propellor/Property.hs | 25 +++++++++++++++++++++++++ src/Propellor/Property/Postfix.hs | 5 +++-- 2 files changed, 28 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/Propellor/Property.hs b/src/Propellor/Property.hs index c58cc9fe..eacb6004 100644 --- a/src/Propellor/Property.hs +++ b/src/Propellor/Property.hs @@ -29,6 +29,7 @@ module Propellor.Property ( , unchecked , changesFile , changesFileContent + , isNewerThan , checkResult , Checkable , assume @@ -230,6 +231,30 @@ changesFileContent p f = checkResult getmd5 comparemd5 p newmd5 <- getmd5 return $ if oldmd5 == newmd5 then NoChange else MadeChange +-- | Determines if the first file is newer than the second file. +-- +-- This can be used with `check` to only run a command when a file +-- has changed. +-- +-- > check ("/etc/aliases" `isNewerThan` "/etc/aliases.db") +-- > (cmdProperty "newaliases" [] `assume` MadeChange) -- updates aliases.db +-- +-- Or it can be used with `checkResult` to test if a command made a change. +-- +-- > checkResult (return ()) +-- > (\_ -> "/etc/aliases.db" `isNewerThan` "/etc/aliases") +-- > (cmdProperty "newaliases" []) +-- +-- (If one of the files does not exist, the file that does exist is +-- considered to be the newer of the two.) +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 + -- | Makes a property that is satisfied differently depending on the host's -- operating system. -- diff --git a/src/Propellor/Property/Postfix.hs b/src/Propellor/Property/Postfix.hs index bcb9fb30..e9fdfc38 100644 --- a/src/Propellor/Property/Postfix.hs +++ b/src/Propellor/Property/Postfix.hs @@ -60,8 +60,9 @@ mappedFile f setup = setup f -- | Run newaliases command, which should be done after changing -- @/etc/aliases@. newaliases :: Property NoInfo -newaliases = cmdProperty "newaliases" [] - `assume` MadeChange +newaliases = check ("/etc/aliases" `isNewerThan` "/etc/aliases.db") $ + cmdProperty "newaliases" [] + `assume` MadeChange -- | The main config file for postfix. mainCfFile :: FilePath -- cgit v1.3-2-g0d8e From 030f13f2d0501c9fb42c8f1efa0a15fa63c94d67 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 6 Dec 2015 14:24:44 -0400 Subject: allow using `check` on a UncheckedProperty, which yields a Property --- src/Propellor/Property.hs | 8 ---- src/Propellor/Property/Apache.hs | 15 +++--- src/Propellor/Property/Apt.hs | 5 +- src/Propellor/Property/Cmd.hs | 5 +- src/Propellor/Property/DebianMirror.hs | 5 +- src/Propellor/Property/DiskImage.hs | 4 +- src/Propellor/Property/Group.hs | 6 +-- src/Propellor/Property/Locale.hs | 8 +--- src/Propellor/Property/Postfix.hs | 7 +-- .../Property/SiteSpecific/GitAnnexBuilder.hs | 1 - src/Propellor/Property/User.hs | 56 ++++++++++------------ src/Propellor/Types/ResultCheck.hs | 29 ++++++++--- 12 files changed, 65 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/Propellor/Property.hs b/src/Propellor/Property.hs index eacb6004..e862fb44 100644 --- a/src/Propellor/Property.hs +++ b/src/Propellor/Property.hs @@ -169,14 +169,6 @@ infixl 1 ==> ensureProperty :: Property NoInfo -> Propellor Result ensureProperty = catchPropellor . propertySatisfy --- | Makes a Property only need to do anything when a test succeeds. -check :: (LiftPropellor m) => m Bool -> Property i -> Property i -check c p = adjustPropertySatisfy p $ \satisfy -> - ifM (liftPropellor c) - ( satisfy - , return NoChange - ) - -- | Tries the first property, but if it fails to work, instead uses -- the second. fallback :: (Combines p1 p2) => p1 -> p2 -> CombinedType p1 p2 diff --git a/src/Propellor/Property/Apache.hs b/src/Propellor/Property/Apache.hs index 626d3879..9e192e84 100644 --- a/src/Propellor/Property/Apache.hs +++ b/src/Propellor/Property/Apache.hs @@ -37,9 +37,8 @@ siteEnabled hn cf = enable disable [ siteAvailable hn cf `requires` installed `onChange` reloaded - , check (not <$> isenabled) $ - cmdProperty "a2ensite" ["--quiet", hn] - `assume` MadeChange + , check (not <$> isenabled) + (cmdProperty "a2ensite" ["--quiet", hn]) `requires` installed `onChange` reloaded ] @@ -63,15 +62,13 @@ siteAvailable hn cf = combineProperties ("apache site available " ++ hn) $ modEnabled :: String -> RevertableProperty NoInfo modEnabled modname = enable disable where - enable = check (not <$> isenabled) $ - cmdProperty "a2enmod" ["--quiet", modname] - `assume` MadeChange + enable = check (not <$> isenabled) + (cmdProperty "a2enmod" ["--quiet", modname]) `describe` ("apache module enabled " ++ modname) `requires` installed `onChange` reloaded - disable = check isenabled $ - cmdProperty "a2dismod" ["--quiet", modname] - `assume` MadeChange + disable = check isenabled + (cmdProperty "a2dismod" ["--quiet", modname]) `describe` ("apache module disabled " ++ modname) `requires` installed `onChange` reloaded diff --git a/src/Propellor/Property/Apt.hs b/src/Propellor/Property/Apt.hs index f25d8ee7..a177c42f 100644 --- a/src/Propellor/Property/Apt.hs +++ b/src/Propellor/Property/Apt.hs @@ -137,7 +137,6 @@ installed' params ps = robustly $ check (isInstallable ps) go `describe` (unwords $ "apt installed":ps) where go = runApt (params ++ ["install"] ++ ps) - `assume` MadeChange installedBackport :: [Package] -> Property NoInfo installedBackport ps = withOS desc $ \o -> case o of @@ -157,10 +156,8 @@ installedMin :: [Package] -> Property NoInfo installedMin = installed' ["--no-install-recommends", "-y"] removed :: [Package] -> Property NoInfo -removed ps = check (or <$> isInstalled' ps) go +removed ps = check (or <$> isInstalled' ps) (runApt (["-y", "remove"] ++ ps)) `describe` (unwords $ "apt removed":ps) - where - go = runApt (["-y", "remove"] ++ ps) `assume` MadeChange buildDep :: [Package] -> Property NoInfo buildDep ps = robustly $ go diff --git a/src/Propellor/Property/Cmd.hs b/src/Propellor/Property/Cmd.hs index 3db00bc1..83414dcb 100644 --- a/src/Propellor/Property/Cmd.hs +++ b/src/Propellor/Property/Cmd.hs @@ -6,11 +6,10 @@ -- -- 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, and you know that whenever it runs, a --- change was made. +-- `cmdProperty` unnecessarily. -- -- > check (not <$> userExists "bob") --- > (cmdProperty "useradd" ["bob"] `assume` MadeChange) +-- > (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 diff --git a/src/Propellor/Property/DebianMirror.hs b/src/Propellor/Property/DebianMirror.hs index 14024a4e..eea7b96f 100644 --- a/src/Propellor/Property/DebianMirror.hs +++ b/src/Propellor/Property/DebianMirror.hs @@ -126,9 +126,8 @@ mirror mirror' = propertyList , User.accountFor (User "debmirror") , File.dirExists dir , File.ownerGroup dir (User "debmirror") (Group "debmirror") - , check (not . and <$> mapM suitemirrored suites) $ - cmdProperty "debmirror" args - `assume` MadeChange + , check (not . and <$> mapM suitemirrored suites) + (cmdProperty "debmirror" args) `describe` "debmirror setup" , Cron.niceJob ("debmirror_" ++ dir) (_debianMirrorCronTimes mirror') (User "debmirror") "/" $ unwords ("/usr/bin/debmirror" : args) diff --git a/src/Propellor/Property/DiskImage.hs b/src/Propellor/Property/DiskImage.hs index 79237e61..6200f856 100644 --- a/src/Propellor/Property/DiskImage.hs +++ b/src/Propellor/Property/DiskImage.hs @@ -289,12 +289,10 @@ grubBooted bios = (Grub.installed' bios, boots) , inchroot "update-initramfs" ["-u"] `assume` MadeChange -- work around for http://bugs.debian.org/802717 - , check haveosprober $ inchroot "chmod" ["-x", osprober] - `assume` MadeChange + , check haveosprober $ inchroot "chmod" ["-x", osprober] , inchroot "update-grub" [] `assume` MadeChange , check haveosprober $ inchroot "chmod" ["+x", osprober] - `assume` MadeChange , inchroot "grub-install" [wholediskloopdev] `assume` MadeChange -- sync all buffered changes out to the disk image diff --git a/src/Propellor/Property/Group.hs b/src/Propellor/Property/Group.hs index 8499d636..f91ef1c2 100644 --- a/src/Propellor/Property/Group.hs +++ b/src/Propellor/Property/Group.hs @@ -5,10 +5,8 @@ import Propellor.Base type GID = Int exists :: Group -> Maybe GID -> Property NoInfo -exists (Group group') mgid = check test $ - cmdProperty "addgroup" (args mgid) - `assume` MadeChange - `describe` unwords ["group", group'] +exists (Group group') mgid = check test (cmdProperty "addgroup" (args mgid)) + `describe` unwords ["group", group'] where groupFile = "/etc/group" test = not . elem group' . words <$> readProcess "cut" ["-d:", "-f1", groupFile] diff --git a/src/Propellor/Property/Locale.hs b/src/Propellor/Property/Locale.hs index 29de8df2..a9fb3514 100644 --- a/src/Propellor/Property/Locale.hs +++ b/src/Propellor/Property/Locale.hs @@ -24,15 +24,11 @@ type LocaleVariable = String selectedFor :: Locale -> [LocaleVariable] -> RevertableProperty NoInfo locale `selectedFor` vars = select deselect where - select = check (not <$> isselected) select' + select = check (not <$> isselected) (cmdProperty "update-locale" selectArgs) `requires` available locale `describe` (locale ++ " locale selected") - select' = cmdProperty "update-locale" selectArgs - `assume` MadeChange - deselect = check isselected deselect' + deselect = check isselected (cmdProperty "update-locale" vars) `describe` (locale ++ " locale deselected") - deselect' = cmdProperty "update-locale" vars - `assume` MadeChange selectArgs = zipWith (++) vars (repeat ('=':locale)) isselected = locale `isSelectedFor` vars diff --git a/src/Propellor/Property/Postfix.hs b/src/Propellor/Property/Postfix.hs index e9fdfc38..1c8684c7 100644 --- a/src/Propellor/Property/Postfix.hs +++ b/src/Propellor/Property/Postfix.hs @@ -60,9 +60,8 @@ mappedFile f setup = setup f -- | Run newaliases command, which should be done after changing -- @/etc/aliases@. newaliases :: Property NoInfo -newaliases = check ("/etc/aliases" `isNewerThan` "/etc/aliases.db") $ - cmdProperty "newaliases" [] - `assume` MadeChange +newaliases = check ("/etc/aliases" `isNewerThan` "/etc/aliases.db") + (cmdProperty "newaliases" []) -- | The main config file for postfix. mainCfFile :: FilePath @@ -76,7 +75,6 @@ mainCf (name, value) = check notset set setting = name ++ "=" ++ value notset = (/= Just value) <$> getMainCf name set = cmdProperty "postconf" ["-e", setting] - `assume` MadeChange -- | Gets a main.cf setting. getMainCf :: String -> IO (Maybe String) @@ -162,7 +160,6 @@ saslAuthdInstalled = setupdaemon dirperm = check (not <$> doesDirectoryExist dir) $ cmdProperty "dpkg-statoverride" [ "--add", "root", "sasl", "710", dir ] - `assume` MadeChange postfixgroup = (User "postfix") `User.hasGroup` (Group "sasl") `onChange` restarted dir = "/var/spool/postfix/var/run/saslauthd" diff --git a/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs b/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs index a34071ce..2312846c 100644 --- a/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs +++ b/src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs @@ -65,7 +65,6 @@ tree buildarch flavor = combineProperties "gitannexbuilder tree" $ props builddircloned = check (not <$> doesDirectoryExist builddir) $ userScriptProperty (User builduser) [ "git clone git://git-annex.branchable.com/ " ++ builddir ] - `assume` MadeChange buildDepsApt :: Property HasInfo buildDepsApt = combineProperties "gitannexbuilder build deps" $ props diff --git a/src/Propellor/Property/User.hs b/src/Propellor/Property/User.hs index 84d20e62..ea88a1b3 100644 --- a/src/Propellor/Property/User.hs +++ b/src/Propellor/Property/User.hs @@ -8,28 +8,26 @@ import qualified Propellor.Property.File as File data Eep = YesReallyDeleteHome accountFor :: User -> Property NoInfo -accountFor user@(User u) = check nohomedir $ - cmdProperty "adduser" +accountFor user@(User u) = check nohomedir go + `describe` ("account for " ++ u) + where + nohomedir = isNothing <$> catchMaybeIO (homedir user) + go = cmdProperty "adduser" [ "--disabled-password" , "--gecos", "" , u ] - `assume` MadeChange - `describe` ("account for " ++ u) - where - nohomedir = isNothing <$> catchMaybeIO (homedir user) -- | Removes user home directory!! Use with caution. nuked :: User -> Eep -> Property NoInfo -nuked user@(User u) _ = check hashomedir $ - cmdProperty "userdel" +nuked user@(User u) _ = check hashomedir go + `describe` ("nuked user " ++ u) + where + hashomedir = isJust <$> catchMaybeIO (homedir user) + go = cmdProperty "userdel" [ "-r" , u ] - `assume` MadeChange - `describe` ("nuked user " ++ u) - where - hashomedir = isJust <$> catchMaybeIO (homedir user) -- | Only ensures that the user has some password set. It may or may -- not be a password from the PrivData. @@ -83,13 +81,13 @@ chpasswd (User user) v ps = makeChange $ withHandle StdinHandle createProcessSuc hClose h lockedPassword :: User -> Property NoInfo -lockedPassword user@(User u) = check (not <$> isLockedPassword user) $ - cmdProperty "passwd" +lockedPassword user@(User u) = check (not <$> isLockedPassword user) go + `describe` ("locked " ++ u ++ " password") + where + go = cmdProperty "passwd" [ "--lock" , u ] - `assume` MadeChange - `describe` ("locked " ++ u ++ " password") data PasswordStatus = NoPassword | LockedPassword | HasPassword deriving (Eq) @@ -109,15 +107,14 @@ homedir :: User -> IO FilePath homedir (User user) = homeDirectory <$> getUserEntryForName user hasGroup :: User -> Group -> Property NoInfo -hasGroup (User user) (Group group') = check test $ - cmdProperty "adduser" +hasGroup (User user) (Group group') = check test go + `describe` unwords ["user", user, "in group", group'] + where + test = not . elem group' . words <$> readProcess "groups" [user] + go = cmdProperty "adduser" [ user , group' ] - `assume` MadeChange - `describe` unwords ["user", user, "in group", group'] - where - test = not . elem group' . words <$> readProcess "groups" [user] -- | Gives a user access to the secondary groups, including audio and -- video, that the OS installer normally gives a desktop user access to. @@ -150,13 +147,11 @@ hasDesktopGroups user@(User u) = property desc $ do -- | Controls whether shadow passwords are enabled or not. shadowConfig :: Bool -> Property NoInfo -shadowConfig True = check (not <$> shadowExists) $ - cmdProperty "shadowconfig" ["on"] - `assume` MadeChange +shadowConfig True = check (not <$> shadowExists) + (cmdProperty "shadowconfig" ["on"]) `describe` "shadow passwords enabled" -shadowConfig False = check shadowExists $ - cmdProperty "shadowconfig" ["off"] - `assume` MadeChange +shadowConfig False = check shadowExists + (cmdProperty "shadowconfig" ["off"]) `describe` "shadow passwords disabled" shadowExists :: IO Bool @@ -168,9 +163,8 @@ hasLoginShell :: User -> FilePath -> Property NoInfo hasLoginShell user loginshell = shellSetTo user loginshell `requires` shellEnabled loginshell shellSetTo :: User -> FilePath -> Property NoInfo -shellSetTo (User u) loginshell = check needchangeshell $ - cmdProperty "chsh" ["--shell", loginshell, u] - `assume` MadeChange +shellSetTo (User u) loginshell = check needchangeshell + (cmdProperty "chsh" ["--shell", loginshell, u]) `describe` (u ++ " has login shell " ++ loginshell) where needchangeshell = do diff --git a/src/Propellor/Types/ResultCheck.hs b/src/Propellor/Types/ResultCheck.hs index 09fbf73b..4c6524ee 100644 --- a/src/Propellor/Types/ResultCheck.hs +++ b/src/Propellor/Types/ResultCheck.hs @@ -4,15 +4,16 @@ module Propellor.Types.ResultCheck ( UncheckedProperty, unchecked, checkResult, + check, Checkable, assume, ) where import Propellor.Types import Propellor.Exception +import Utility.Monad import Data.Monoid -import Control.Monad.IO.Class (liftIO) -- | This is a `Property` but its `Result` is not accurate; in particular -- it may return `NoChange` despite having made a change. @@ -29,30 +30,44 @@ unchecked = UncheckedProperty -- `UncheckedProperty` to a `Property`, but can also be used to further -- check a `Property`. checkResult - :: Checkable p i - => IO a + :: (Checkable p i, LiftPropellor m) + => m a -- ^ Run before ensuring the property. - -> (a -> IO Result) + -> (a -> m Result) -- ^ Run after ensuring the property. Return `MadeChange` if a -- change was detected, or `NoChange` if no change was detected. -> p i -> Property i checkResult precheck postcheck p = adjustPropertySatisfy (checkedProp p) $ \satisfy -> do - a <- liftIO precheck + a <- liftPropellor precheck r <- catchPropellor satisfy -- Always run postcheck, even if the result is already MadeChange, -- as it may need to clean up after precheck. - r' <- liftIO $ postcheck a + r' <- liftPropellor $ postcheck a return (r <> r') - + +-- | Makes a `Property` or an `UncheckedProperty` only run +-- when a test succeeds. +check :: (Checkable p i, LiftPropellor m) => m Bool -> p i -> Property i +check test p = adjustPropertySatisfy (preCheckedProp p) $ \satisfy -> + ifM (liftPropellor test) + ( satisfy + , return NoChange + ) + class Checkable p i where checkedProp :: p i -> Property i + preCheckedProp :: p i -> Property i instance Checkable Property i where checkedProp = id + preCheckedProp = id instance Checkable UncheckedProperty i where checkedProp (UncheckedProperty p) = p + -- Since it was pre-checked that the property needed to be run, + -- if the property succeeded, we can assume it made a change. + preCheckedProp (UncheckedProperty p) = p `assume` MadeChange -- | Sometimes it's not practical to test if a property made a change. -- In such a case, it's often fine to say: -- cgit v1.3-2-g0d8e