From 0ee04ecc43e047b00437fb660e71f7dd67dd3afc Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sat, 24 Jan 2015 22:38:10 -0400 Subject: GADT properties seem to work (untested) * Property has been converted to a GADT, and will be Property NoInfo or Property HasInfo. This was done to make sure that ensureProperty is only used on properties that do not have Info. Transition guide: - Change all "Property" to "Property NoInfo" or "Property WithInfo" (The compiler can tell you if you got it wrong!) - To construct a RevertableProperty, it is useful to use the new () operator - Constructing a list of properties can be problimatic, since Property NoInto and Property WithInfo are different types and cannot appear in the same list. To deal with this, "props" has been added, and can built up a list of properties of different types, using the same (&) and (!) operators that are used to build up a host's properties. --- src/Propellor/Property/Apt.hs | 52 +++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 24 deletions(-) (limited to 'src/Propellor/Property/Apt.hs') diff --git a/src/Propellor/Property/Apt.hs b/src/Propellor/Property/Apt.hs index 2dd9ca16..d567d0ec 100644 --- a/src/Propellor/Property/Apt.hs +++ b/src/Propellor/Property/Apt.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE FlexibleContexts #-} + module Propellor.Property.Apt where import Data.Maybe @@ -77,36 +79,36 @@ securityUpdates suite -- -- Since the CDN is sometimes unreliable, also adds backup lines using -- kernel.org. -stdSourcesList :: Property +stdSourcesList :: Property NoInfo stdSourcesList = withOS ("standard sources.list") $ \o -> case o of (Just (System (Debian suite) _)) -> ensureProperty $ stdSourcesListFor suite _ -> error "os is not declared to be Debian" -stdSourcesListFor :: DebianSuite -> Property +stdSourcesListFor :: DebianSuite -> Property NoInfo stdSourcesListFor suite = stdSourcesList' suite [] -- | Adds additional sources.list generators. -- -- Note that if a Property needs to enable an apt source, it's better -- to do so via a separate file in -stdSourcesList' :: DebianSuite -> [SourcesGenerator] -> Property +stdSourcesList' :: DebianSuite -> [SourcesGenerator] -> Property NoInfo stdSourcesList' suite more = setSourcesList (concatMap (\gen -> gen suite) generators) `describe` ("standard sources.list for " ++ show suite) where generators = [debCdn, kernelOrg, securityUpdates] ++ more -setSourcesList :: [Line] -> Property +setSourcesList :: [Line] -> Property NoInfo setSourcesList ls = sourcesList `File.hasContent` ls `onChange` update -setSourcesListD :: [Line] -> FilePath -> Property +setSourcesListD :: [Line] -> FilePath -> Property NoInfo setSourcesListD ls basename = f `File.hasContent` ls `onChange` update where f = "/etc/apt/sources.list.d/" ++ basename ++ ".list" -runApt :: [String] -> Property +runApt :: [String] -> Property NoInfo runApt ps = cmdProperty' "apt-get" ps noninteractiveEnv noninteractiveEnv :: [(String, String)] @@ -115,26 +117,26 @@ noninteractiveEnv = , ("APT_LISTCHANGES_FRONTEND", "none") ] -update :: Property +update :: Property NoInfo update = runApt ["update"] `describe` "apt update" -upgrade :: Property +upgrade :: Property NoInfo upgrade = runApt ["-y", "dist-upgrade"] `describe` "apt dist-upgrade" type Package = String -installed :: [Package] -> Property +installed :: [Package] -> Property NoInfo installed = installed' ["-y"] -installed' :: [String] -> [Package] -> Property +installed' :: [String] -> [Package] -> Property NoInfo installed' params ps = robustly $ check (isInstallable ps) go `describe` (unwords $ "apt installed":ps) where go = runApt $ params ++ ["install"] ++ ps -installedBackport :: [Package] -> Property +installedBackport :: [Package] -> Property NoInfo installedBackport ps = trivial $ withOS desc $ \o -> case o of Nothing -> error "cannot install backports; os not declared" (Just (System (Debian suite) _)) -> case backportSuite suite of @@ -147,16 +149,16 @@ installedBackport ps = trivial $ withOS desc $ \o -> case o of notsupported o = error $ "backports not supported on " ++ show o -- | Minimal install of package, without recommends. -installedMin :: [Package] -> Property +installedMin :: [Package] -> Property NoInfo installedMin = installed' ["--no-install-recommends", "-y"] -removed :: [Package] -> Property +removed :: [Package] -> Property NoInfo removed ps = check (or <$> isInstalled' ps) go `describe` (unwords $ "apt removed":ps) where go = runApt $ ["-y", "remove"] ++ ps -buildDep :: [Package] -> Property +buildDep :: [Package] -> Property NoInfo buildDep ps = robustly go `describe` (unwords $ "apt build-dep":ps) where @@ -165,7 +167,7 @@ buildDep ps = robustly go -- | Installs the build deps for the source package unpacked -- in the specifed directory, with a dummy package also -- installed so that autoRemove won't remove them. -buildDepIn :: FilePath -> Property +buildDepIn :: FilePath -> Property NoInfo buildDepIn dir = go `requires` installedMin ["devscripts", "equivs"] where go = cmdProperty' "sh" ["-c", "cd '" ++ dir ++ "' && mk-build-deps debian/control --install --tool 'apt-get -y --no-install-recommends' --remove"] @@ -173,11 +175,13 @@ buildDepIn dir = go `requires` installedMin ["devscripts", "equivs"] -- | Package installation may fail becuse the archive has changed. -- Run an update in that case and retry. -robustly :: Property -> Property -robustly p = adjustProperty p $ \satisfy -> do +robustly :: (Combines (Property i) (Property NoInfo)) => Property i -> Property i +robustly p = adjustPropertySatisfy p $ \satisfy -> do r <- satisfy if r == FailedChange - then ensureProperty $ p `requires` update + -- Safe to use ignoreInfo because we're re-running + -- the same property. + then ensureProperty $ ignoreInfo $ p `requires` update else return r isInstallable :: [Package] -> IO Bool @@ -203,13 +207,13 @@ isInstalled' ps = catMaybes . map parse . lines <$> policy environ <- addEntry "LANG" "C" <$> getEnvironment readProcessEnv "apt-cache" ("policy":ps) (Just environ) -autoRemove :: Property +autoRemove :: Property NoInfo autoRemove = runApt ["-y", "autoremove"] `describe` "apt autoremove" -- | Enables unattended upgrades. Revert to disable. unattendedUpgrades :: RevertableProperty -unattendedUpgrades = RevertableProperty enable disable +unattendedUpgrades = enable disable where enable = setup True `before` Service.running "cron" @@ -237,7 +241,7 @@ unattendedUpgrades = RevertableProperty enable disable -- | Preseeds debconf values and reconfigures the package so it takes -- effect. -reConfigure :: Package -> [(String, String, String)] -> Property +reConfigure :: Package -> [(String, String, String)] -> Property NoInfo reConfigure package vals = reconfigure `requires` setselections `describe` ("reconfigure " ++ package) where @@ -253,7 +257,7 @@ reConfigure package vals = reconfigure `requires` setselections -- -- Assumes that there is a 1:1 mapping between service names and apt -- package names. -serviceInstalledRunning :: Package -> Property +serviceInstalledRunning :: Package -> Property NoInfo serviceInstalledRunning svc = Service.running svc `requires` installed [svc] data AptKey = AptKey @@ -262,7 +266,7 @@ data AptKey = AptKey } trustsKey :: AptKey -> RevertableProperty -trustsKey k = RevertableProperty trust untrust +trustsKey k = trust untrust where desc = "apt trusts key " ++ keyname k f = "/etc/apt/trusted.gpg.d" keyname k ++ ".gpg" @@ -276,6 +280,6 @@ trustsKey k = RevertableProperty trust untrust -- | Cleans apt's cache of downloaded packages to avoid using up disk -- space. -cacheCleaned :: Property +cacheCleaned :: Property NoInfo cacheCleaned = trivial $ cmdProperty "apt-get" ["clean"] `describe` "apt cache cleaned" -- cgit v1.3-2-g0d8e From e9d5d9aff1cc2046149d3e5dcd9f4ef0f2a334a1 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 25 Jan 2015 14:45:14 -0400 Subject: remove toSimpleProp It didn't do what I thought it did with a RevertableProperty; it always returned Nothing because even if the input properties to are NoInfo, it casts them to HasInfo. Even if it had worked, it lost type safety. Better to export the Property NoInfo that is used in a RevertableProperty, so it can be used directly. --- doc/todo/RevertableProperty_with_NoInfo.mdwn | 18 ++++++++++++ src/Propellor/Property/Apt.hs | 25 +++++++++++------ src/Propellor/Property/Debootstrap.hs | 41 +++++++++++++++------------- src/Propellor/Property/OS.hs | 4 +-- src/Propellor/Property/Obnam.hs | 2 +- src/Propellor/Types.hs | 4 --- 6 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 doc/todo/RevertableProperty_with_NoInfo.mdwn (limited to 'src/Propellor/Property/Apt.hs') diff --git a/doc/todo/RevertableProperty_with_NoInfo.mdwn b/doc/todo/RevertableProperty_with_NoInfo.mdwn new file mode 100644 index 00000000..3b4a61a9 --- /dev/null +++ b/doc/todo/RevertableProperty_with_NoInfo.mdwn @@ -0,0 +1,18 @@ +Currently, a RevertableProperty's Properties always both HasInfo. This +means that if a Property NoInfo is updated to be a RevertableProperty, and +someplace called ensureProperty on it, that will refuse to compile. + +The workaround is generally to export the original NoInfo property under +a different name, so it can still be used with ensureProperty. + +This could be fixed: + + data RevertableProperty i1 i2 where + RProp :: Property i1 -> Property i2 -> RevertableProperty i1 i2 + +However, needing to write "RevertableProperty HasInfo NoInfo" is quite +a mouthful! + +Since only 2 places in the propellor source code currently need to deal +with this, it doesn't currently seem worth making the change, unless a less +intrusive way can be found. diff --git a/src/Propellor/Property/Apt.hs b/src/Propellor/Property/Apt.hs index d567d0ec..75c59772 100644 --- a/src/Propellor/Property/Apt.hs +++ b/src/Propellor/Property/Apt.hs @@ -266,17 +266,24 @@ data AptKey = AptKey } trustsKey :: AptKey -> RevertableProperty -trustsKey k = trust untrust +trustsKey k = trustsKey' k untrustKey k + +trustsKey' :: AptKey -> Property NoInfo +trustsKey' k = check (not <$> doesFileExist f) $ property desc $ makeChange $ do + withHandle StdinHandle createProcessSuccess + (proc "gpg" ["--no-default-keyring", "--keyring", f, "--import", "-"]) $ \h -> do + hPutStr h (pubkey k) + hClose h + nukeFile $ f ++ "~" -- gpg dropping where desc = "apt trusts key " ++ keyname k - f = "/etc/apt/trusted.gpg.d" keyname k ++ ".gpg" - untrust = File.notPresent f - trust = check (not <$> doesFileExist f) $ property desc $ makeChange $ do - withHandle StdinHandle createProcessSuccess - (proc "gpg" ["--no-default-keyring", "--keyring", f, "--import", "-"]) $ \h -> do - hPutStr h (pubkey k) - hClose h - nukeFile $ f ++ "~" -- gpg dropping + f = aptKeyFile k + +untrustKey :: AptKey -> Property NoInfo +untrustKey = File.notPresent . aptKeyFile + +aptKeyFile :: AptKey -> FilePath +aptKeyFile k = "/etc/apt/trusted.gpg.d" keyname k ++ ".gpg" -- | Cleans apt's cache of downloaded packages to avoid using up disk -- space. diff --git a/src/Propellor/Property/Debootstrap.hs b/src/Propellor/Property/Debootstrap.hs index 3feb280c..d4947ab7 100644 --- a/src/Propellor/Property/Debootstrap.hs +++ b/src/Propellor/Property/Debootstrap.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE FlexibleContexts #-} + module Propellor.Property.Debootstrap ( Url, DebootstrapConfig(..), @@ -56,18 +58,18 @@ toParams (c1 :+ c2) = toParams c1 <> toParams c2 -- Note that reverting this property does not stop any processes -- currently running in the chroot. built :: FilePath -> System -> DebootstrapConfig -> RevertableProperty -built = built' (toProp installed) - -built' :: Property HasInfo -> FilePath -> System -> DebootstrapConfig -> RevertableProperty -built' installprop target system@(System _ arch) config = setup teardown +built target system config = built' (toProp installed) target system config teardown where - setup = check (unpopulated target <||> ispartial) setupprop - `requires` installprop - teardown = check (not <$> unpopulated target) teardownprop - unpopulated d = null <$> catchDefaultIO [] (dirContents d) + teardownprop = property ("removed debootstrapped " ++ target) $ + makeChange (removetarget target) +built' :: (Combines (Property NoInfo) (Property i)) => Property i -> FilePath -> System -> DebootstrapConfig -> Property (CInfo NoInfo i) +built' installprop target system@(System _ arch) config = + check (unpopulated target <||> ispartial) setupprop + `requires` installprop + where setupprop = property ("debootstrapped " ++ target) $ liftIO $ do createDirectoryIfMissing True target -- Don't allow non-root users to see inside the chroot, @@ -92,24 +94,25 @@ built' installprop target system@(System _ arch) config = setup teardown , return FailedChange ) - teardownprop = property ("removed debootstrapped " ++ target) $ - makeChange removetarget - - removetarget = do - submnts <- filter (\p -> simplifyPath p /= simplifyPath target) - . filter (dirContains target) - <$> mountPoints - forM_ submnts umountLazy - removeDirectoryRecursive target - -- A failed debootstrap run will leave a debootstrap directory; -- recover by deleting it and trying again. ispartial = ifM (doesDirectoryExist (target "debootstrap")) ( do - removetarget + removetarget target return True , return False ) + +unpopulated :: FilePath -> IO Bool +unpopulated d = null <$> catchDefaultIO [] (dirContents d) + +removetarget :: FilePath -> IO () +removetarget target = do + submnts <- filter (\p -> simplifyPath p /= simplifyPath target) + . filter (dirContains target) + <$> mountPoints + forM_ submnts umountLazy + removeDirectoryRecursive target extractSuite :: System -> Maybe String extractSuite (System (Debian s) _) = Just $ Apt.showSuite s diff --git a/src/Propellor/Property/OS.hs b/src/Propellor/Property/OS.hs index 710428d4..7a6857fb 100644 --- a/src/Propellor/Property/OS.hs +++ b/src/Propellor/Property/OS.hs @@ -89,10 +89,10 @@ cleanInstallOnce confirmation = check (not <$> doesFileExist flagfile) $ (Just u@(System (Ubuntu _) _)) -> debootstrap u _ -> error "os is not declared to be Debian or Ubuntu" - debootstrap targetos = ensureProperty $ fromJust $ toSimpleProp $ + debootstrap targetos = ensureProperty $ -- Ignore the os setting, and install debootstrap from -- source, since we don't know what OS we're running in yet. - Debootstrap.built' (toProp Debootstrap.sourceInstall) + Debootstrap.built' Debootstrap.sourceInstall newOSDir targetos Debootstrap.DefaultConfig -- debootstrap, I wish it was faster.. -- TODO eatmydata to speed it up diff --git a/src/Propellor/Property/Obnam.hs b/src/Propellor/Property/Obnam.hs index 9d283527..adaf255c 100644 --- a/src/Propellor/Property/Obnam.hs +++ b/src/Propellor/Property/Obnam.hs @@ -118,7 +118,7 @@ latestVersion :: Property NoInfo latestVersion = withOS "obnam latest version" $ \o -> case o of (Just (System (Debian suite) _)) | isStable suite -> ensureProperty $ Apt.setSourcesListD (stablesources suite) "obnam" - `requires` (fromJust (toSimpleProp (Apt.trustsKey key))) + `requires` Apt.trustsKey' key _ -> noChange where stablesources suite = diff --git a/src/Propellor/Types.hs b/src/Propellor/Types.hs index 9a0e22ab..ba63cf9d 100644 --- a/src/Propellor/Types.hs +++ b/src/Propellor/Types.hs @@ -179,7 +179,6 @@ class IsProp p where -- | Sets description. describe :: p -> Desc -> p toProp :: p -> Property HasInfo - toSimpleProp :: p -> Maybe (Property NoInfo) getDesc :: p -> Desc -- | Gets the info of the property, combined with all info -- of all children properties. @@ -188,7 +187,6 @@ class IsProp p where instance IsProp (Property HasInfo) where describe (IProperty _ a i cs) d = IProperty d a i cs toProp = id - toSimpleProp _ = Nothing getDesc = propertyDesc getInfoRecursive (IProperty _ _ i cs) = i <> mconcat (map getInfoRecursive cs) @@ -196,7 +194,6 @@ instance IsProp (Property NoInfo) where describe (SProperty _ a cs) d = SProperty d a cs toProp = toIProperty getDesc = propertyDesc - toSimpleProp = Just getInfoRecursive _ = mempty instance IsProp RevertableProperty where @@ -205,7 +202,6 @@ instance IsProp RevertableProperty where RevertableProperty (describe p1 d) (describe p2 ("not " ++ d)) getDesc (RevertableProperty p1 _) = getDesc p1 toProp (RevertableProperty p1 _) = p1 - toSimpleProp = toSimpleProp . toProp -- | Return the Info of the currently active side. getInfoRecursive (RevertableProperty p1 _p2) = getInfoRecursive p1 -- cgit v1.3-2-g0d8e