packages feed

propellor 2.7.3 → 2.8.0

raw patch · 52 files changed

+1017/−368 lines, 52 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Propellor.Types: Info :: Val System -> Set (PrivDataField, Maybe PrivDataSourceDesc, HostContext) -> Map SshKeyType String -> Set HostName -> Set Record -> NamedConfMap -> DockerInfo Host -> ChrootInfo Host -> Info
- Propellor.Types: _aliases :: Info -> Set HostName
- Propellor.Types: _chrootinfo :: Info -> ChrootInfo Host
- Propellor.Types: _dns :: Info -> Set Record
- Propellor.Types: _dockerinfo :: Info -> DockerInfo Host
- Propellor.Types: _namedconf :: Info -> NamedConfMap
- Propellor.Types: _os :: Info -> Val System
- Propellor.Types: _privData :: Info -> Set (PrivDataField, Maybe PrivDataSourceDesc, HostContext)
- Propellor.Types: _sshPubKey :: Info -> Map SshKeyType String
- Propellor.Types: instance Empty Info
- Propellor.Types: instance Monoid Info
- Propellor.Types: instance Show Info
- Propellor.Types.Chroot: instance Empty (ChrootInfo host)
- Propellor.Types.Chroot: instance Monoid (ChrootInfo host)
- Propellor.Types.Chroot: instance Show host => Show (ChrootInfo host)
- Propellor.Types.Docker: instance Empty (DockerInfo h)
- Propellor.Types.Docker: instance Monoid (DockerInfo h)
- Propellor.Types.Docker: instance Show h => Show (DockerInfo h)
- Propellor.Types.PrivData: type PrivData = String
- Propellor.Types.Val: NoVal :: Val a
- Propellor.Types.Val: Val :: a -> Val a
- Propellor.Types.Val: data Val a
- Propellor.Types.Val: fromVal :: Val a -> Maybe a
- Propellor.Types.Val: instance Empty (Val a)
- Propellor.Types.Val: instance Eq a => Eq (Val a)
- Propellor.Types.Val: instance Monoid (Val a)
- Propellor.Types.Val: instance Show a => Show (Val a)
+ Propellor.Info: pureInfoProperty' :: Desc -> Info -> Property HasInfo
+ Propellor.PrivData: data PrivInfo
+ Propellor.PrivData: instance Eq PrivInfo
+ Propellor.PrivData: instance IsInfo PrivInfo
+ Propellor.PrivData: instance Monoid PrivInfo
+ Propellor.PrivData: instance Ord PrivInfo
+ Propellor.PrivData: instance Show PrivInfo
+ Propellor.PrivData: instance Typeable PrivInfo
+ Propellor.Property.Aiccu: confPath :: FilePath
+ Propellor.Property.Aiccu: hasConfig :: TunnelId -> UserName -> Property HasInfo
+ Propellor.Property.Aiccu: installed :: Property NoInfo
+ Propellor.Property.Aiccu: restarted :: Property NoInfo
+ Propellor.Property.Aiccu: type TunnelId = String
+ Propellor.Property.Aiccu: type UserName = String
+ Propellor.Property.Apt: type DebconfTemplate = String
+ Propellor.Property.Apt: type DebconfTemplateType = String
+ Propellor.Property.Apt: type DebconfTemplateValue = String
+ Propellor.Property.DebianMirror: Essential :: DebianPriority
+ Propellor.Property.DebianMirror: Extra :: DebianPriority
+ Propellor.Property.DebianMirror: Important :: DebianPriority
+ Propellor.Property.DebianMirror: Optional :: DebianPriority
+ Propellor.Property.DebianMirror: Required :: DebianPriority
+ Propellor.Property.DebianMirror: Standard :: DebianPriority
+ Propellor.Property.DebianMirror: data DebianPriority
+ Propellor.Property.DebianMirror: instance Eq DebianPriority
+ Propellor.Property.DebianMirror: instance Show DebianPriority
+ Propellor.Property.DebianMirror: mirror :: Url -> FilePath -> [DebianSuite] -> [Architecture] -> [Section] -> Bool -> [DebianPriority] -> Times -> Property NoInfo
+ Propellor.Property.DebianMirror: mirrorCdn :: FilePath -> [DebianSuite] -> [Architecture] -> [Section] -> Bool -> [DebianPriority] -> Times -> Property NoInfo
+ Propellor.Property.DebianMirror: showPriority :: DebianPriority -> String
+ Propellor.Property.File: hasContentProtected :: FilePath -> [Line] -> Property NoInfo
+ Propellor.Property.Rsync: Exclude :: Pattern -> Filter
+ Propellor.Property.Rsync: Include :: Pattern -> Filter
+ Propellor.Property.Rsync: Pattern :: String -> Pattern
+ Propellor.Property.Rsync: class RsyncParam p
+ Propellor.Property.Rsync: data Filter
+ Propellor.Property.Rsync: filesUnder :: FilePath -> Pattern
+ Propellor.Property.Rsync: instance RsyncParam Filter
+ Propellor.Property.Rsync: newtype Pattern
+ Propellor.Property.Rsync: rsync :: [String] -> Property NoInfo
+ Propellor.Property.Rsync: syncDir :: Src -> Dest -> Property NoInfo
+ Propellor.Property.Rsync: syncDirFiltered :: [Filter] -> Src -> Dest -> Property NoInfo
+ Propellor.Property.Rsync: toRsync :: RsyncParam p => p -> String
+ Propellor.Property.Rsync: type Dest = FilePath
+ Propellor.Property.Rsync: type Src = FilePath
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: type Flavor = Maybe String
+ Propellor.Property.Ssh: instance Eq SshPubKeyInfo
+ Propellor.Property.Ssh: instance IsInfo SshPubKeyInfo
+ Propellor.Property.Ssh: instance Monoid SshPubKeyInfo
+ Propellor.Property.Ssh: instance Ord SshPubKeyInfo
+ Propellor.Property.Ssh: instance Typeable SshPubKeyInfo
+ Propellor.Property.Unbound: cachingDnsServer :: [UnboundSection] -> [UnboundZone] -> [UnboundHost] -> Property NoInfo
+ Propellor.Property.Unbound: installed :: Property NoInfo
+ Propellor.Property.Unbound: reloaded :: Property NoInfo
+ Propellor.Property.Unbound: restarted :: Property NoInfo
+ Propellor.Types: instance Typeable Host
+ Propellor.Types.Chroot: instance Empty ChrootInfo
+ Propellor.Types.Chroot: instance IsInfo ChrootInfo
+ Propellor.Types.Chroot: instance Monoid ChrootInfo
+ Propellor.Types.Chroot: instance Show ChrootInfo
+ Propellor.Types.Chroot: instance Typeable ChrootInfo
+ Propellor.Types.CmdLine: RmKey :: String -> CmdLine
+ Propellor.Types.Dns: AliasesInfo :: (Set HostName) -> AliasesInfo
+ Propellor.Types.Dns: DnsInfo :: Set Record -> DnsInfo
+ Propellor.Types.Dns: PTR :: ReverseIP -> Record
+ Propellor.Types.Dns: canonicalIP :: IPAddr -> IPAddr
+ Propellor.Types.Dns: fromAliasesInfo :: AliasesInfo -> [HostName]
+ Propellor.Types.Dns: fromDnsInfo :: DnsInfo -> Set Record
+ Propellor.Types.Dns: instance Eq AliasesInfo
+ Propellor.Types.Dns: instance Eq DnsInfo
+ Propellor.Types.Dns: instance IsInfo AliasesInfo
+ Propellor.Types.Dns: instance IsInfo DnsInfo
+ Propellor.Types.Dns: instance IsInfo NamedConfMap
+ Propellor.Types.Dns: instance Monoid AliasesInfo
+ Propellor.Types.Dns: instance Monoid DnsInfo
+ Propellor.Types.Dns: instance Ord AliasesInfo
+ Propellor.Types.Dns: instance Ord DnsInfo
+ Propellor.Types.Dns: instance Show AliasesInfo
+ Propellor.Types.Dns: instance Show DnsInfo
+ Propellor.Types.Dns: instance Typeable AliasesInfo
+ Propellor.Types.Dns: instance Typeable DnsInfo
+ Propellor.Types.Dns: instance Typeable NamedConfMap
+ Propellor.Types.Dns: instance Typeable Record
+ Propellor.Types.Dns: newtype AliasesInfo
+ Propellor.Types.Dns: newtype DnsInfo
+ Propellor.Types.Dns: reverseIP :: IPAddr -> ReverseIP
+ Propellor.Types.Dns: toAliasesInfo :: [HostName] -> AliasesInfo
+ Propellor.Types.Dns: toDnsInfo :: Set Record -> DnsInfo
+ Propellor.Types.Dns: type ReverseIP = String
+ Propellor.Types.Docker: instance Empty DockerInfo
+ Propellor.Types.Docker: instance IsInfo DockerInfo
+ Propellor.Types.Docker: instance Monoid DockerInfo
+ Propellor.Types.Docker: instance Show DockerInfo
+ Propellor.Types.Docker: instance Typeable DockerInfo
+ Propellor.Types.Info: InfoVal :: v -> InfoVal v
+ Propellor.Types.Info: NoInfoVal :: InfoVal v
+ Propellor.Types.Info: addInfo :: IsInfo v => Info -> v -> Info
+ Propellor.Types.Info: class (Typeable v, Monoid v) => IsInfo v
+ Propellor.Types.Info: class Typeable (a :: k)
+ Propellor.Types.Info: data Info
+ Propellor.Types.Info: data InfoVal v
+ Propellor.Types.Info: fromInfoVal :: InfoVal v -> Maybe v
+ Propellor.Types.Info: getInfo :: IsInfo v => Info -> v
+ Propellor.Types.Info: instance Monoid (InfoVal v)
+ Propellor.Types.Info: instance Monoid Info
+ Propellor.Types.Info: instance Show Info
+ Propellor.Types.Info: instance Typeable InfoVal
+ Propellor.Types.Info: instance Typeable v => IsInfo (InfoVal v)
+ Propellor.Types.Info: propigatableInfo :: Info -> Info
+ Propellor.Types.Info: propigateInfo :: IsInfo v => v -> Bool
+ Propellor.Types.OS: instance Typeable System
+ Propellor.Types.PrivData: PrivData :: String -> PrivData
+ Propellor.Types.PrivData: newtype PrivData
+ Propellor.Types.PrivData: privDataByteString :: PrivData -> ByteString
+ Propellor.Types.PrivData: privDataLines :: PrivData -> [String]
+ Propellor.Types.PrivData: privDataVal :: PrivData -> String
- Propellor.Info: askInfo :: (Info -> Val a) -> Propellor (Maybe a)
+ Propellor.Info: askInfo :: IsInfo v => Propellor v
- Propellor.Info: pureInfoProperty :: Desc -> Info -> Property HasInfo
+ Propellor.Info: pureInfoProperty :: IsInfo v => Desc -> v -> Property HasInfo
- Propellor.PrivData: type PrivMap = Map (PrivDataField, Context) PrivData
+ Propellor.PrivData: type PrivMap = Map (PrivDataField, Context) String
- Propellor.Property.Apt: reConfigure :: Package -> [(String, String, String)] -> Property NoInfo
+ Propellor.Property.Apt: reConfigure :: Package -> [(DebconfTemplate, DebconfTemplateType, DebconfTemplateValue)] -> Property NoInfo
- Propellor.Property.SiteSpecific.GitAnnexBuilder: armAutoBuilder :: System -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: armAutoBuilder :: System -> Flavor -> Property HasInfo
- Propellor.Property.SiteSpecific.GitAnnexBuilder: autoBuilderContainer :: (System -> Property HasInfo) -> System -> Times -> TimeOut -> Container
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: autoBuilderContainer :: (System -> Flavor -> Property HasInfo) -> System -> Flavor -> Times -> TimeOut -> Container
- Propellor.Property.SiteSpecific.GitAnnexBuilder: standardAutoBuilder :: System -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: standardAutoBuilder :: System -> Flavor -> Property HasInfo
- Propellor.Property.SiteSpecific.GitAnnexBuilder: tree :: Architecture -> Property HasInfo
+ Propellor.Property.SiteSpecific.GitAnnexBuilder: tree :: Architecture -> Flavor -> Property HasInfo
- Propellor.Property.Ssh: getPubKey :: Propellor (Map SshKeyType String)
+ Propellor.Property.Ssh: getPubKey :: Propellor (Map SshKeyType PubKeyText)
- Propellor.Types.Chroot: ChrootInfo :: Map FilePath host -> ChrootCfg -> ChrootInfo host
+ Propellor.Types.Chroot: ChrootInfo :: Map FilePath Host -> ChrootCfg -> ChrootInfo
- Propellor.Types.Chroot: _chrootCfg :: ChrootInfo host -> ChrootCfg
+ Propellor.Types.Chroot: _chrootCfg :: ChrootInfo -> ChrootCfg
- Propellor.Types.Chroot: _chroots :: ChrootInfo host -> Map FilePath host
+ Propellor.Types.Chroot: _chroots :: ChrootInfo -> Map FilePath Host
- Propellor.Types.Chroot: data ChrootInfo host
+ Propellor.Types.Chroot: data ChrootInfo
- Propellor.Types.Docker: DockerInfo :: [DockerRunParam] -> Map String h -> DockerInfo h
+ Propellor.Types.Docker: DockerInfo :: [DockerRunParam] -> Map String Host -> DockerInfo
- Propellor.Types.Docker: _dockerContainers :: DockerInfo h -> Map String h
+ Propellor.Types.Docker: _dockerContainers :: DockerInfo -> Map String Host
- Propellor.Types.Docker: _dockerRunParams :: DockerInfo h -> [DockerRunParam]
+ Propellor.Types.Docker: _dockerRunParams :: DockerInfo -> [DockerRunParam]
- Propellor.Types.Docker: data DockerInfo h
+ Propellor.Types.Docker: data DockerInfo

Files

CHANGELOG view
@@ -1,3 +1,35 @@+propellor (2.8.0) unstable; urgency=medium++  * Added Propellor.Property.Rsync.+  * Convert Info to use Data.Dynamic, so properties can export and consume+    info of any type that is Typeable and a Monoid, including data types+    private to a module. (API change)+    Thanks to Joachim Breitner for the idea.+  * Improve propellor wrapper to better handle installation cloning+    the public propellor repo, by setting that repo to be upstream,+    so propellor doesnt try to push to a read-only repo.+  * Added DebianMirror module, contributed by Félix Sipma.+  * Some hlint cleanups.+    Thanks, Mario Lang+  * Added Propellor.Property.Unbound for the caching DNS server.+    Thanks, Félix Sipma.+  * Added PTR to Dns.Record. While this is ignored by+    Propellor.Property.Dns for now, since reverse DNS setup is not+    implemented there yet, it can be used in other places, eg Unbound.+    Thanks, Félix Sipma.+  * PrivData converted to newtype (API change).+  * Stopped stripping trailing newlines when setting PrivData;+    this was previously done to avoid mistakes when pasting eg passwords+    with an unwanted newline. Instead, PrivData consumers should use either+    privDataLines or privDataVal, to extract respectively lines or a+    value (without internal newlines) from PrivData.+  * Allow storing arbitrary ByteStrings in PrivData, extracted using+    privDataByteString.+  * Added Aiccu module, contributed by Jelmer Vernooij.+  * Added --rm-key.++ -- Joey Hess <id@joeyh.name>  Tue, 22 Sep 2015 19:35:07 -0400+ propellor (2.7.3) unstable; urgency=medium    * Fix bug that caused provisioning new chroots to fail.@@ -67,7 +99,7 @@   * Add docker image related properties.     Thanks, Antoine Eiche.   * Export CommandParam, boolSystem, safeSystem, shellEscape, and-  * createProcess from Propellor.Property.Cmd, so they are available+    createProcess from Propellor.Property.Cmd, so they are available     for use in constricting your own Properties when using propellor     as a library.   * Improve enter-machine scripts for systemd-nspawn containers to unset most@@ -319,6 +351,7 @@   * Apache: Fix daemon reload when enabling a new module or site.   * Docker: Stop using docker.io; that was a compat symlink in     the Debian package which has been removed in docker.io 1.3.1~dfsg1-2.+    Closes: #769452   * Orphaned the Debian package, as I am retiring from Debian.   -- Joey Hess <joeyh@debian.org>  Sat, 08 Nov 2014 15:57:36 -0400
config-joey.hs view
@@ -26,6 +26,7 @@ import qualified Propellor.Property.Systemd as Systemd import qualified Propellor.Property.Journald as Journald import qualified Propellor.Property.Chroot as Chroot+import qualified Propellor.Property.Aiccu as Aiccu import qualified Propellor.Property.OS as OS import qualified Propellor.Property.HostingProvider.CloudAtCost as CloudAtCost import qualified Propellor.Property.HostingProvider.Linode as Linode@@ -73,7 +74,8 @@  darkstar :: Host darkstar = host "darkstar.kitenet.net"-	& ipv6 "2001:4830:1600:187::2" -- sixxs tunnel+	& ipv6 "2001:4830:1600:187::2"+	& Aiccu.hasConfig "T18376" "JHZ2-SIXXS"  	& Apt.buildDep ["git-annex"] `period` Daily @@ -83,7 +85,7 @@ 	& imageBuilt "/tmp/img" c MSDOS 		[ partition EXT2 `mountedAt` "/boot" `setFlag` BootFlag 		, partition EXT4 `mountedAt` "/" `addFreeSpace` MegaBytes 100-		, swapPartition (MegaBytes 256)+		-- , swapPartition (MegaBytes 256) 		] noFinalization -- (grubBooted PC)   where 	c d = Chroot.debootstrapped (System (Debian Unstable) "amd64") mempty d@@ -142,10 +144,13 @@  	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer 		GitAnnexBuilder.standardAutoBuilder-		(System (Debian Testing) "amd64") fifteenpast "2h")+		(System (Debian Unstable) "amd64") Nothing fifteenpast "2h") 	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer 		GitAnnexBuilder.standardAutoBuilder-		(System (Debian Testing) "i386") fifteenpast "2h")+		(System (Debian Unstable) "i386") Nothing fifteenpast "2h")+	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer+		GitAnnexBuilder.standardAutoBuilder+		(System (Debian (Stable "jessie")) "i386") (Just "ancient") fifteenpast "2h") 	& Systemd.nspawned (GitAnnexBuilder.androidAutoBuilderContainer 		(Cron.Times "1 1 * * *") "3h")   where@@ -177,7 +182,7 @@  	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer 		GitAnnexBuilder.armAutoBuilder-			(System (Debian Unstable) "armel") Cron.Daily "22h")+			(System (Debian Unstable) "armel") Nothing Cron.Daily "22h")  -- This is not a complete description of kite, since it's a -- multiuser system with eg, user passwords that are not deployed
debian/changelog view
@@ -1,3 +1,35 @@+propellor (2.8.0) unstable; urgency=medium++  * Added Propellor.Property.Rsync.+  * Convert Info to use Data.Dynamic, so properties can export and consume+    info of any type that is Typeable and a Monoid, including data types+    private to a module. (API change)+    Thanks to Joachim Breitner for the idea.+  * Improve propellor wrapper to better handle installation cloning+    the public propellor repo, by setting that repo to be upstream,+    so propellor doesnt try to push to a read-only repo.+  * Added DebianMirror module, contributed by Félix Sipma.+  * Some hlint cleanups.+    Thanks, Mario Lang+  * Added Propellor.Property.Unbound for the caching DNS server.+    Thanks, Félix Sipma.+  * Added PTR to Dns.Record. While this is ignored by+    Propellor.Property.Dns for now, since reverse DNS setup is not+    implemented there yet, it can be used in other places, eg Unbound.+    Thanks, Félix Sipma.+  * PrivData converted to newtype (API change).+  * Stopped stripping trailing newlines when setting PrivData;+    this was previously done to avoid mistakes when pasting eg passwords+    with an unwanted newline. Instead, PrivData consumers should use either+    privDataLines or privDataVal, to extract respectively lines or a+    value (without internal newlines) from PrivData.+  * Allow storing arbitrary ByteStrings in PrivData, extracted using+    privDataByteString.+  * Added Aiccu module, contributed by Jelmer Vernooij.+  * Added --rm-key.++ -- Joey Hess <id@joeyh.name>  Tue, 22 Sep 2015 19:35:07 -0400+ propellor (2.7.3) unstable; urgency=medium    * Fix bug that caused provisioning new chroots to fail.@@ -67,7 +99,7 @@   * Add docker image related properties.     Thanks, Antoine Eiche.   * Export CommandParam, boolSystem, safeSystem, shellEscape, and-  * createProcess from Propellor.Property.Cmd, so they are available+    createProcess from Propellor.Property.Cmd, so they are available     for use in constricting your own Properties when using propellor     as a library.   * Improve enter-machine scripts for systemd-nspawn containers to unset most@@ -319,6 +351,7 @@   * Apache: Fix daemon reload when enabling a new module or site.   * Docker: Stop using docker.io; that was a compat symlink in     the Debian package which has been removed in docker.io 1.3.1~dfsg1-2.+    Closes: #769452   * Orphaned the Debian package, as I am retiring from Debian.   -- Joey Hess <joeyh@debian.org>  Sat, 08 Nov 2014 15:57:36 -0400
debian/control view
@@ -42,7 +42,7 @@ 	git, 	make, Description: property-based host configuration management in haskell- Propellor enures that the system it's run in satisfies a list of+ Propellor ensures that the system it's run in satisfies a list of  properties, taking action as necessary when a property is not yet met.  .  It is configured using haskell.
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 2.7.3+Version: 2.8.0 Cabal-Version: >= 1.8 License: BSD3 Maintainer: Joey Hess <id@joeyh.name>@@ -27,7 +27,7 @@   debian/lintian-overrides Synopsis: property-based host configuration management in haskell Description:- Propellor enures that the system it's run in satisfies a list of+ Propellor ensures that the system it's run in satisfies a list of  properties, taking action as necessary when a property is not yet met.  .  It is configured using haskell.@@ -70,6 +70,7 @@   Exposed-Modules:     Propellor     Propellor.Property+    Propellor.Property.Aiccu     Propellor.Property.Apache     Propellor.Property.Apt     Propellor.Property.Cmd@@ -77,6 +78,7 @@     Propellor.Property.Chroot     Propellor.Property.ConfFile     Propellor.Property.Cron+    Propellor.Property.DebianMirror     Propellor.Property.Debootstrap     Propellor.Property.DiskImage     Propellor.Property.Dns@@ -100,6 +102,7 @@     Propellor.Property.Postfix     Propellor.Property.Prosody     Propellor.Property.Reboot+    Propellor.Property.Rsync     Propellor.Property.List     Propellor.Property.LightDM     Propellor.Property.Scheduled@@ -109,6 +112,7 @@     Propellor.Property.Systemd     Propellor.Property.Systemd.Core     Propellor.Property.Tor+    Propellor.Property.Unbound     Propellor.Property.User     Propellor.Property.HostingProvider.CloudAtCost     Propellor.Property.HostingProvider.DigitalOcean@@ -131,9 +135,9 @@     Propellor.Types.Docker     Propellor.Types.Dns     Propellor.Types.Empty+    Propellor.Types.Info     Propellor.Types.OS     Propellor.Types.PrivData-    Propellor.Types.Val     Propellor.Types.Result     Propellor.Types.CmdLine   Other-Modules:
src/Propellor/CmdLine.hs view
@@ -26,6 +26,7 @@ 	, "  propellor hostname" 	, "  propellor --spin targethost [--via relayhost]" 	, "  propellor --add-key keyid"+	, "  propellor --rm-key keyid" 	, "  propellor --set field context" 	, "  propellor --dump field context" 	, "  propellor --edit field context"@@ -50,6 +51,7 @@ 			<*> pure (Just r) 		_ -> Spin <$> mapM hostname ps <*> pure Nothing 	go ("--add-key":k:[]) = return $ AddKey k+	go ("--rm-key":k:[]) = return $ RmKey k 	go ("--set":f:c:[]) = withprivfield f c Set 	go ("--unset":f:c:[]) = withprivfield f c Unset 	go ("--dump":f:c:[]) = withprivfield f c Dump@@ -100,6 +102,7 @@ 	go _ (Edit field context) = editPrivData field context 	go _ ListFields = listPrivDataFields hostlist 	go _ (AddKey keyid) = addKey keyid+	go _ (RmKey keyid) = rmKey keyid 	go _ c@(ChrootChain _ _ _ _) = Chroot.chain hostlist c 	go _ (DockerChain hn cid) = Docker.chain hostlist hn cid 	go _ (DockerInit hn) = Docker.init hn
src/Propellor/Gpg.hs view
@@ -14,6 +14,7 @@ import Utility.Monad import Utility.Misc import Utility.Tmp+import Utility.FileSystemEncoding  type KeyId = String @@ -40,10 +41,10 @@ addKey :: KeyId -> IO () addKey keyid = exitBool =<< allM (uncurry actionMessage) 	[ ("adding key to propellor's keyring", addkeyring)-	, ("staging propellor's keyring", gitadd keyring)-	, ("updating encryption of any privdata", reencryptprivdata)-	, ("configuring git signing to use key", gitconfig)-	, ("committing changes", gitcommit)+	, ("staging propellor's keyring", gitAdd keyring)+	, ("updating encryption of any privdata", reencryptPrivData)+	, ("configuring git commit signing to use key", gitconfig)+	, ("committing changes", gitCommitKeyRing "add-key") 	]   where 	addkeyring = do@@ -54,18 +55,6 @@ 				unwords (useKeyringOpts ++ ["--import"]) 			] -	reencryptprivdata = ifM (doesFileExist privDataFile)-		( do-			gpgEncrypt privDataFile =<< gpgDecrypt privDataFile-			gitadd privDataFile-		, return True-		)--	gitadd f = boolSystem "git"-		[ Param "add"-		, File f-		]- 	gitconfig = ifM (snd <$> processTranscript "gpg" ["--list-secret-keys", keyid] Nothing) 		( boolSystem "git" 			[ Param "config"@@ -77,12 +66,53 @@ 			return True 		) -	gitcommit = gitCommit-		[ File keyring-		, Param "-m"-		, Param "propellor addkey"+rmKey :: KeyId -> IO ()+rmKey keyid = exitBool =<< allM (uncurry actionMessage)+	[ ("removing key from propellor's keyring", rmkeyring)+	, ("staging propellor's keyring", gitAdd keyring)+	, ("updating encryption of any privdata", reencryptPrivData)+	, ("configuring git commit signing to not use key", gitconfig)+	, ("committing changes", gitCommitKeyRing "rm-key")+	]+  where+	rmkeyring = boolSystem "gpg" $+		(map Param useKeyringOpts) ++ +		[ Param "--batch"+		, Param "--yes"+		, Param "--delete-key", Param keyid 		]+	+	gitconfig = ifM ((==) (keyid++"\n", True) <$> processTranscript "git" ["config", "user.signingkey"] Nothing)+		( boolSystem "git"+			[ Param "config"+			, Param "--unset"+			, Param "user.signingkey"+			]+		, return True+		)	 +reencryptPrivData :: IO Bool+reencryptPrivData = ifM (doesFileExist privDataFile)+	( do+		gpgEncrypt privDataFile =<< gpgDecrypt privDataFile+		gitAdd privDataFile+	, return True+	)+	+gitAdd :: FilePath -> IO Bool+gitAdd f = boolSystem "git"+	[ Param "add"+	, File f+	]++gitCommitKeyRing :: String -> IO Bool+gitCommitKeyRing action = gitCommit+	[ File keyring+	, File privDataFile+	, Param "-m"+	, Param ("propellor " ++ action)+	]+ -- Adds --gpg-sign if there's a keyring. gpgSignParams :: [CommandParam] -> IO [CommandParam] gpgSignParams ps = ifM (doesFileExist keyring)@@ -98,7 +128,7 @@  gpgDecrypt :: FilePath -> IO String gpgDecrypt f = ifM (doesFileExist f)-	( readProcess "gpg" ["--decrypt", f]+	( writeReadProcessEnv "gpg" ["--decrypt", f] Nothing Nothing (Just fileEncoding) 	, return "" 	) @@ -112,8 +142,9 @@ 		, "--encrypt" 		, "--trust-model", "always" 		] ++ concatMap (\k -> ["--recipient", k]) keyids-	encrypted <- writeReadProcessEnv "gpg" opts-		Nothing-		(Just $ flip hPutStr s)-		Nothing+	encrypted <- writeReadProcessEnv "gpg" opts Nothing (Just writer) Nothing 	viaTmp writeFile f encrypted+  where+	writer h = do+		fileEncoding h+		hPutStr h s
src/Propellor/Info.hs view
@@ -3,7 +3,7 @@ module Propellor.Info where  import Propellor.Types-import Propellor.Types.Val+import Propellor.Types.Info  import "mtl" Control.Monad.Reader import qualified Data.Set as S@@ -12,23 +12,26 @@ import Data.Monoid import Control.Applicative -pureInfoProperty :: Desc -> Info -> Property HasInfo-pureInfoProperty desc i = infoProperty ("has " ++ desc) (return NoChange) i mempty+pureInfoProperty :: (IsInfo v) => Desc -> v -> Property HasInfo+pureInfoProperty desc v = pureInfoProperty' desc (addInfo mempty v) -askInfo :: (Info -> Val a) -> Propellor (Maybe a)-askInfo f = asks (fromVal . f . hostInfo)+pureInfoProperty' :: Desc -> Info -> Property HasInfo+pureInfoProperty' desc i = infoProperty ("has " ++ desc) (return NoChange) i mempty +-- | Gets a value from the host's Info.+askInfo :: (IsInfo v) => Propellor v+askInfo = asks (getInfo . hostInfo)+ -- | Specifies the operating system of a host. -- -- This only provides info for other Properties, so they can act--- conditional on the os.+-- conditionally on the os. os :: System -> Property HasInfo-os system = pureInfoProperty ("Operating " ++ show system) $-	mempty { _os = Val system }+os system = pureInfoProperty ("Operating " ++ show system) (InfoVal system)  --  Gets the operating system of a host, if it has been specified. getOS :: Propellor (Maybe System)-getOS = askInfo _os+getOS = fromInfoVal <$> askInfo  -- | Indidate that a host has an A record in the DNS. --@@ -53,15 +56,14 @@ -- problems with CNAMEs, and also means that when multiple hosts have the -- same alias, a DNS round-robin is automatically set up. alias :: Domain -> Property HasInfo-alias d = pureInfoProperty ("alias " ++ d) $ mempty-	{ _aliases = S.singleton d+alias d = pureInfoProperty' ("alias " ++ d) $ mempty+	`addInfo` toAliasesInfo [d] 	-- A CNAME is added here, but the DNS setup code converts it to an 	-- IP address when that makes sense.-	, _dns = S.singleton $ CNAME $ AbsDomain d-	} +	`addInfo` (toDnsInfo $ S.singleton $ CNAME $ AbsDomain d)  addDNS :: Record -> Property HasInfo-addDNS r = pureInfoProperty (rdesc r) $ mempty { _dns = S.singleton r }+addDNS r = pureInfoProperty (rdesc r) (toDnsInfo (S.singleton r))   where 	rdesc (CNAME d) = unwords ["alias", ddesc d] 	rdesc (Address (IPv4 addr)) = unwords ["ipv4", addr]@@ -72,20 +74,21 @@ 	rdesc (SRV x y z d) = unwords ["SRV", show x, show y, show z, ddesc d] 	rdesc (SSHFP x y s) = unwords ["SSHFP", show x, show y, s] 	rdesc (INCLUDE f) = unwords ["$INCLUDE", f]+	rdesc (PTR x) = unwords ["PTR", x]  	ddesc (AbsDomain domain) = domain 	ddesc (RelDomain domain) = domain 	ddesc RootDomain = "@"  hostMap :: [Host] -> M.Map HostName Host-hostMap l = M.fromList $ zip (map hostName l) l +hostMap l = M.fromList $ zip (map hostName l) l  aliasMap :: [Host] -> M.Map HostName Host aliasMap = M.fromList . concat .-	map (\h -> map (\aka -> (aka, h)) $ S.toList $ _aliases $ hostInfo h)+	map (\h -> map (\aka -> (aka, h)) $ fromAliasesInfo $ getInfo $ hostInfo h)  findHost :: [Host] -> HostName -> Maybe Host-findHost l hn = maybe (findAlias l hn) Just (findHostNoAlias l hn)+findHost l hn = (findHostNoAlias l hn) <|> (findAlias l hn)  findHostNoAlias :: [Host] -> HostName -> Maybe Host findHostNoAlias l hn = M.lookup hn (hostMap l)@@ -94,9 +97,7 @@ findAlias l hn = M.lookup hn (aliasMap l)  getAddresses :: Info -> [IPAddr]-getAddresses = mapMaybe getIPAddr . S.toList . _dns+getAddresses = mapMaybe getIPAddr . S.toList . fromDnsInfo . getInfo  hostAddresses :: HostName -> [Host] -> [IPAddr]-hostAddresses hn hosts = case hostInfo <$> findHost hosts hn of-	Nothing -> []-	Just info -> mapMaybe getIPAddr $ S.toList $ _dns info+hostAddresses hn hosts = maybe [] (getAddresses . hostInfo) (findHost hosts hn)
src/Propellor/Message.hs view
@@ -112,8 +112,7 @@ 	go (Just "1") = enableDebugMode 	go (Just _) = noop 	go Nothing = whenM (doesDirectoryExist ".git") $-		whenM (any (== "1") . lines <$> getgitconfig) $-			enableDebugMode+		whenM (elem "1" . lines <$> getgitconfig) enableDebugMode 	getgitconfig = catchDefaultIO "" $ 		readProcess "git" ["config", "propellor.debug"] 
src/Propellor/PrivData.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE PackageImports #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}  module Propellor.PrivData ( 	withPrivData,@@ -14,6 +16,7 @@ 	makePrivDataDir, 	decryptPrivData, 	PrivMap,+	PrivInfo, ) where  import Control.Applicative@@ -22,14 +25,17 @@ import Data.Maybe import Data.Monoid import Data.List+import Data.Typeable import Control.Monad import Control.Monad.IfElse import "mtl" Control.Monad.Reader import qualified Data.Map as M import qualified Data.Set as S+import qualified Data.ByteString.Lazy as L  import Propellor.Types import Propellor.Types.PrivData+import Propellor.Types.Info import Propellor.Message import Propellor.Info import Propellor.Gpg@@ -43,6 +49,7 @@ import Utility.FileMode import Utility.Env import Utility.Table+import Utility.FileSystemEncoding  -- | Allows a Property to access the value of a specific PrivDataField, -- for use in a specific Context or HostContext.@@ -51,7 +58,7 @@ -- -- > withPrivData (PrivFile pemfile) (Context "joeyh.name") $ \getdata -> -- >     property "joeyh.name ssl cert" $ getdata $ \privdata ->--- >       liftIO $ writeFile pemfile privdata+-- >       liftIO $ writeFile pemfile (privDataVal privdata) -- >   where pemfile = "/etc/ssl/certs/web.pem" --  -- Note that if the value is not available, the action is not run@@ -102,9 +109,10 @@ 	addinfo p = infoProperty 		(propertyDesc p) 		(propertySatisfy p)-		(propertyInfo p <> mempty { _privData = privset })+		(propertyInfo p `addInfo` privset) 		(propertyChildren p)-	privset = S.fromList $ map (\s -> (privDataField s, describePrivDataSource s, hc)) srclist+	privset = PrivInfo $ S.fromList $+		map (\s -> (privDataField s, describePrivDataSource s, hc)) srclist 	fieldnames = map show fieldlist 	fieldlist = map privDataField srclist 	hc = asHostContext c@@ -116,8 +124,7 @@ 	putStrLn ""  addPrivData :: (PrivDataField, Maybe PrivDataSourceDesc, HostContext) -> Property HasInfo-addPrivData v = pureInfoProperty (show v) $-	mempty { _privData = S.singleton v }+addPrivData v = pureInfoProperty (show v) (PrivInfo (S.singleton v))  {- Gets the requested field's value, in the specified context if it's  - available, from the host's local privdata cache. -}@@ -127,22 +134,25 @@   where 	localcache = catchDefaultIO Nothing $ readish <$> readFile privDataLocal -type PrivMap = M.Map (PrivDataField, Context) PrivData+type PrivMap = M.Map (PrivDataField, Context) String  -- | Get only the set of PrivData that the Host's Info says it uses. filterPrivData :: Host -> PrivMap -> PrivMap filterPrivData host = M.filterWithKey (\k _v -> S.member k used)   where 	used = S.map (\(f, _, c) -> (f, mkHostContext c (hostName host))) $-		_privData $ hostInfo host+		fromPrivInfo $ getInfo $ hostInfo host  getPrivData :: PrivDataField -> Context -> PrivMap -> Maybe PrivData-getPrivData field context = M.lookup (field, context)+getPrivData field context m = do+	s <- M.lookup (field, context) m+	return (PrivData s)  setPrivData :: PrivDataField -> Context -> IO () setPrivData field context = do 	putStrLn "Enter private data on stdin; ctrl-D when done:"-	setPrivDataTo field context =<< hGetContentsStrict stdin+	fileEncoding stdin+	setPrivDataTo field context . PrivData =<< hGetContentsStrict stdin  unsetPrivData :: PrivDataField -> Context -> IO () unsetPrivData field context = do@@ -150,20 +160,21 @@ 	putStrLn "Private data unset."  dumpPrivData :: PrivDataField -> Context -> IO ()-dumpPrivData field context =-	maybe (error "Requested privdata is not set.") putStrLn+dumpPrivData field context = do+	maybe (error "Requested privdata is not set.")+		(L.hPut stdout . privDataByteString) 		=<< (getPrivData field context <$> decryptPrivData)  editPrivData :: PrivDataField -> Context -> IO () editPrivData field context = do 	v <- getPrivData field context <$> decryptPrivData-	v' <- withTmpFile "propellorXXXX" $ \f h -> do-		hClose h-		maybe noop (writeFileProtected f) v+	v' <- withTmpFile "propellorXXXX" $ \f th -> do+		hClose th+		maybe noop (\p -> writeFileProtected' f (`L.hPut` privDataByteString p)) v 		editor <- getEnvDefault "EDITOR" "vi" 		unlessM (boolSystem editor [File f]) $ 			error "Editor failed; aborting."-		readFile f+		PrivData <$> readFile f 	setPrivDataTo field context v'  listPrivDataFields :: [Host] -> IO ()@@ -182,29 +193,26 @@ 		showSet $ map (\(f, c) -> (f, c, join $ M.lookup (f, c) descmap)) missing   where 	header = ["Field", "Context", "Used by"]-	mkrow k@(field, (Context context)) =+	mkrow k@(field, Context context) = 		[ shellEscape $ show field 		, shellEscape context 		, intercalate ", " $ sort $ fromMaybe [] $ M.lookup k usedby 		] 	mkhostmap host mkv = M.fromList $ map (\(f, d, c) -> ((f, mkHostContext c (hostName host)), mkv d)) $-		S.toList $ _privData $ hostInfo host-	usedby = M.unionsWith (++) $ map (\h -> mkhostmap h $ const $ [hostName h]) hosts+		S.toList $ fromPrivInfo $ getInfo $ hostInfo host+	usedby = M.unionsWith (++) $ map (\h -> mkhostmap h $ const [hostName h]) hosts 	wantedmap = M.fromList $ zip (M.keys usedby) (repeat "")-	descmap = M.unions $ map (\h -> mkhostmap h id) hosts+	descmap = M.unions $ map (`mkhostmap` id) hosts 	section desc = putStrLn $ "\n" ++ desc 	showtable rows = do 		putStr $ unlines $ formatTable $ tableWithHeader header rows  setPrivDataTo :: PrivDataField -> Context -> PrivData -> IO ()-setPrivDataTo field context value = do+setPrivDataTo field context (PrivData value) = do 	modifyPrivData set 	putStrLn "Private data set."   where-	set = M.insert (field, context) (chomp value)-	chomp s-		| end s == "\n" = chomp (beginning s)-		| otherwise = s+	set = M.insert (field, context) value  modifyPrivData :: (PrivMap -> PrivMap) -> IO () modifyPrivData f = do@@ -219,3 +227,12 @@  makePrivDataDir :: IO () makePrivDataDir = createDirectoryIfMissing False privDataDir++newtype PrivInfo = PrivInfo+	{ fromPrivInfo :: S.Set (PrivDataField, Maybe PrivDataSourceDesc, HostContext) }+	deriving (Eq, Ord, Show, Typeable, Monoid)++-- PrivInfo is propigated out of containers, so that propellor can see which+-- hosts need it.+instance IsInfo PrivInfo where+	propigateInfo _ = True
src/Propellor/PropAccum.hs view
@@ -13,6 +13,7 @@  import Propellor.Types import Propellor.Property+import Propellor.Types.Info  -- | Starts accumulating the properties of a Host. --@@ -47,7 +48,7 @@ instance PropAccum Host where 	(Host hn ps is) &  p = Host hn (ps ++ [toProp p]) 		(is <> getInfoRecursive p)-	(Host hn ps is) &^ p = Host hn ([toProp p] ++ ps)+	(Host hn ps is) &^ p = Host hn (toProp p : ps) 		(getInfoRecursive p <> is) 	getProperties = hostProperties @@ -71,12 +72,6 @@ --  -- The Info of the propertyChildren is adjusted to only include  -- info that should be propigated out to the Property.------ DNS Info is propigated, so that eg, aliases of a PropAccum--- are reflected in the dns for the host where it runs.------ PrivData Info is propigated, so that properties used inside a--- PropAccum will have the necessary PrivData available. propigateContainer 	:: (PropAccum container) 	=> container@@ -90,10 +85,6 @@   where 	hostprops = map go $ getProperties c 	go p = -		let i = propertyInfo p-		    i' = mempty-			{ _dns = _dns i-			, _privData = _privData i-			}+		let i = propigatableInfo (propertyInfo p) 		    cs = map go (propertyChildren p)-		in infoProperty (propertyDesc p) (propertySatisfy p) i' cs+		in infoProperty (propertyDesc p) (propertySatisfy p) i cs
src/Propellor/Property.hs view
@@ -67,8 +67,7 @@         -> Property x         -> Property y         -> CombinedType (Property x) (Property y)-onChangeFlagOnFail flagfile p1 p2 =-	combineWith go p1 p2+onChangeFlagOnFail flagfile = combineWith go   where 	go s1 s2 = do 		r1 <- s1
+ src/Propellor/Property/Aiccu.hs view
@@ -0,0 +1,50 @@+module Propellor.Property.Aiccu (+	installed,+	restarted,+	confPath,+	UserName,+	TunnelId,+	hasConfig,+) where++import Propellor+import qualified Propellor.Property.Apt as Apt+import qualified Propellor.Property.Service as Service+import qualified Propellor.Property.File as File++installed :: Property NoInfo+installed = Apt.installed ["aiccu"]++restarted :: Property NoInfo+restarted = Service.restarted "aiccu"++confPath :: FilePath+confPath = "/etc/aiccu.conf"++type TunnelId = String++config :: UserName -> TunnelId -> PrivData -> [File.Line]+config u t p = +	[ "protocol tic"+	, "server tic.sixxs.net"+	, "username " ++ u+	, "password " ++ privDataVal p+	, "ipv6_interface sixxs"+	, "tunnel_id " ++ t+	, "daemonize true"+	, "automatic true"+	, "requiretls true"+	, "makebeats true"+	]++-- | Configures an ipv6 tunnel using sixxs.net, with the given TunneId+-- and sixx.net UserName.+hasConfig :: TunnelId -> UserName -> Property HasInfo+hasConfig t u = prop  `onChange` restarted+  where+	prop = withSomePrivData [(Password (u++"/"++t)), (Password u)] (Context "aiccu") $+		property "aiccu configured" . writeConfig+	writeConfig :: (((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Propellor Result+	writeConfig getpassword = getpassword $ ensureProperty . go+	go (Password _, p) = confPath `File.hasContentProtected` config u t p+	go (f, _) = error $ "Unexpected type of privdata: " ++ show f
src/Propellor/Property/Apt.hs view
@@ -80,7 +80,7 @@ -- Since the CDN is sometimes unreliable, also adds backup lines using -- kernel.org. stdSourcesList :: Property NoInfo-stdSourcesList = withOS ("standard sources.list") $ \o ->+stdSourcesList = withOS "standard sources.list" $ \o -> 	case o of 		(Just (System (Debian suite) _)) -> 			ensureProperty $ stdSourcesListFor suite@@ -187,7 +187,7 @@ isInstallable :: [Package] -> IO Bool isInstallable ps = do 	l <- isInstalled' ps-	return $ any (== False) l && not (null l)+	return $ elem False l && not (null l)  isInstalled :: Package -> IO Bool isInstalled p = (== [True]) <$> isInstalled' [p]@@ -197,7 +197,7 @@ -- even vary. If apt does not know about a package at all, it will not -- be included in the result list. isInstalled' :: [Package] -> IO [Bool]-isInstalled' ps = catMaybes . map parse . lines <$> policy+isInstalled' ps = (mapMaybe parse . lines) <$> policy   where 	parse l 		| "Installed: (none)" `isInfixOf` l = Just False@@ -239,18 +239,25 @@ 					("Unattended-Upgrade::Origins-Pattern { \"o=Debian,a="++showSuite suite++"\"; };") 			_ -> noChange +type DebconfTemplate = String+type DebconfTemplateType = String+type DebconfTemplateValue = String+ -- | Preseeds debconf values and reconfigures the package so it takes -- effect.-reConfigure :: Package -> [(String, String, String)] -> Property NoInfo+reConfigure :: Package -> [(DebconfTemplate, DebconfTemplateType, DebconfTemplateValue)] -> Property NoInfo reConfigure package vals = reconfigure `requires` setselections 	`describe` ("reconfigure " ++ package)   where-	setselections = property "preseed" $ makeChange $-		withHandle StdinHandle createProcessSuccess-			(proc "debconf-set-selections" []) $ \h -> do-				forM_ vals $ \(tmpl, tmpltype, value) ->-					hPutStrLn h $ unwords [package, tmpl, tmpltype, value]-				hClose h+	setselections = property "preseed" $ +		if null vals +			then noChange+			else makeChange $+				withHandle StdinHandle createProcessSuccess+					(proc "debconf-set-selections" []) $ \h -> do+						forM_ vals $ \(tmpl, tmpltype, value) ->+							hPutStrLn h $ unwords [package, tmpl, tmpltype, value]+						hClose h 	reconfigure = cmdPropertyEnv "dpkg-reconfigure" ["-fnone", package] noninteractiveEnv  -- | Ensures that a service is installed and running.
src/Propellor/Property/Chroot.hs view
@@ -15,6 +15,7 @@ import Propellor import Propellor.Types.CmdLine import Propellor.Types.Chroot+import Propellor.Types.Info import Propellor.Property.Chroot.Util import qualified Propellor.Property.Debootstrap as Debootstrap import qualified Propellor.Property.Systemd.Core as Systemd@@ -91,8 +92,8 @@ 		(propertyChildren p)  chrootInfo :: Chroot -> Info-chrootInfo (Chroot loc _ _ h) =-	mempty { _chrootinfo = mempty { _chroots = M.singleton loc h } }+chrootInfo (Chroot loc _ _ h) = mempty `addInfo` +	mempty { _chroots = M.singleton loc h }  -- | Propellor is run inside the chroot to provision it. propellChroot :: Chroot -> ([String] -> IO (CreateProcess, IO ())) -> Bool -> Property NoInfo@@ -143,7 +144,7 @@ chain hostlist (ChrootChain hn loc systemdonly onconsole) =  	case findHostNoAlias hostlist hn of 		Nothing -> errorMessage ("cannot find host " ++ hn)-		Just parenthost -> case M.lookup loc (_chroots $ _chrootinfo $ hostInfo parenthost) of+		Just parenthost -> case M.lookup loc (_chroots $ getInfo $ hostInfo parenthost) of 			Nothing -> errorMessage ("cannot find chroot " ++ loc ++ " on host " ++ hn) 			Just h -> go h   where
src/Propellor/Property/ConfFile.hs view
@@ -38,8 +38,7 @@ 	-> InsertSection 	-> FilePath 	-> Property NoInfo-adjustSection desc start past adjust insert f =-	fileProperty desc go f+adjustSection desc start past adjust insert = fileProperty desc go   where 	go ls = let (pre, wanted, post) = foldl' find ([], [], []) ls 		in if null wanted
+ src/Propellor/Property/DebianMirror.hs view
@@ -0,0 +1,61 @@+module Propellor.Property.DebianMirror+	( DebianPriority(..)+	, showPriority+	, mirror+	, mirrorCdn+	) where++import Propellor+import qualified Propellor.Property.File as File+import qualified Propellor.Property.Apt as Apt+import qualified Propellor.Property.Cron as Cron+import qualified Propellor.Property.User as User++import Data.List+++data DebianPriority = Essential | Required | Important | Standard | Optional | Extra+	deriving (Show, Eq)++showPriority :: DebianPriority -> String+showPriority Essential = "essential"+showPriority Required  = "required"+showPriority Important = "important"+showPriority Standard  = "standard"+showPriority Optional  = "optional"+showPriority Extra     = "extra"++mirror :: Apt.Url -> FilePath -> [DebianSuite] -> [Architecture] -> [Apt.Section] -> Bool -> [DebianPriority] -> Cron.Times -> Property NoInfo+mirror url dir suites archs sections source priorities crontimes = propertyList+	("Debian mirror " ++ dir)+	[ Apt.installed ["debmirror"]+	, User.accountFor (User "debmirror")+	, File.dirExists dir+	, File.ownerGroup dir (User "debmirror") (Group "debmirror")+	, check (not . and <$> mapM suitemirrored suites) $ cmdProperty "debmirror" args+		`describe` "debmirror setup"+	, Cron.niceJob ("debmirror_" ++ dir) crontimes (User "debmirror") "/" $+		unwords ("/usr/bin/debmirror" : args)+	]+  where+	suitemirrored suite = doesDirectoryExist $ dir </> "dists" </> Apt.showSuite suite+	architecturearg = intercalate ","+	suitearg = intercalate "," $ map Apt.showSuite suites+	priorityRegex pp = "(" ++ intercalate "|" (map showPriority pp) ++ ")"+	args =+		[ "--dist" , suitearg+		, "--arch", architecturearg archs+		, "--section", intercalate "," sections+		, "--limit-priority", "\"" ++ priorityRegex priorities ++ "\""+		]+		+++		(if source then [] else ["--nosource"])+		+++		[ "--host", url+		, "--method", "http"+		, "--keyring", "/usr/share/keyrings/debian-archive-keyring.gpg"+		, dir+		]++mirrorCdn :: FilePath -> [DebianSuite] -> [Architecture] -> [Apt.Section] -> Bool -> [DebianPriority] -> Cron.Times -> Property NoInfo+mirrorCdn = mirror "http://httpredir.debian.org/debian"
src/Propellor/Property/Debootstrap.hs view
@@ -158,7 +158,7 @@ 	let indexfile = tmpd </> "index.html" 	unlessM (download baseurl indexfile) $ 		errorMessage $ "Failed to download " ++ baseurl-	urls <- reverse . sort -- highest version first+	urls <- sortBy (flip compare) -- highest version first 		. filter ("debootstrap_" `isInfixOf`) 		. filter (".tar." `isInfixOf`) 		. extractUrls baseurl <$>
src/Propellor/Property/DiskImage.hs view
@@ -1,6 +1,10 @@ -- | Disk image generation.  -- -- This module is designed to be imported unqualified.+--+-- TODO run final+-- +-- TODO avoid starting services while populating chroot and running final  module Propellor.Property.DiskImage ( 	-- * Properties@@ -41,8 +45,11 @@ import qualified Propellor.Property.Apt as Apt import Propellor.Property.Parted import Propellor.Property.Mount+import Propellor.Property.Partition+import Propellor.Property.Rsync import Utility.Path +import Data.List (isPrefixOf) import qualified Data.Map.Strict as M import qualified Data.ByteString.Lazy as L import System.Posix.Files@@ -64,8 +71,10 @@ -- > 		& Apt.installed ["linux-image-amd64"] -- >		& ... -- > in imageBuilt "/srv/images/foo.img" chroot MSDOS --- >		[ partition EXT2 `mountedAt` "/boot" `setFlag` BootFlag--- >		, partition EXT4 `mountedAt` "/" `addFreeSpace` MegaBytes 100+-- >		[ partition EXT2 `mountedAt` "/boot"+-- >			`setFlag` BootFlag+-- >		, partition EXT4 `mountedAt` "/"+-- >			`addFreeSpace` MegaBytes 100 -- >		, swapPartition (MegaBytes 256) -- >		] (grubBooted PC) imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> [PartSpec] -> Finalization -> RevertableProperty@@ -100,27 +109,51 @@ -- | Builds a disk image from the contents of a chroot. -- -- The passed property is run inside the mounted disk image.------ TODO copy in--- TODO run final imageBuiltFrom :: DiskImage -> FilePath -> TableType -> [PartSpec] -> Property NoInfo -> RevertableProperty imageBuiltFrom img chrootdir tabletype partspec final = mkimg <!> rmimg   where-	mkimg = property (img ++ " built from " ++ chrootdir) $ do+	desc = img ++ " built from " ++ chrootdir+	mkimg = property desc $ do 		-- unmount helper filesystems such as proc from the chroot 		-- before getting sizes 		liftIO $ unmountBelow chrootdir 		szm <- M.mapKeys (toSysDir chrootdir) . M.map toPartSize  			<$> liftIO (dirSizes chrootdir)-		let calcsz = \mnts -> fromMaybe defSz . getMountSz szm mnts+		let calcsz mnts = maybe defSz fudge . getMountSz szm mnts 		-- tie the knot! 		let (mnts, t) = fitChrootSize tabletype partspec (map (calcsz mnts) mnts) 		ensureProperty $ 			imageExists img (partTableSize t) 				`before` 			partitioned YesReallyDeleteDiskContents img t+				`before`+			kpartx img (partitionsPopulated chrootdir mnts) 	rmimg = File.notPresent img +partitionsPopulated :: FilePath -> [MountPoint] -> [FilePath] -> Property NoInfo+partitionsPopulated chrootdir mnts devs = property desc $ mconcat $ zipWith go mnts devs+  where+	desc = "partitions populated from " ++ chrootdir++	go Nothing _ = noChange+	go (Just mnt) dev = withTmpDir "mnt" $ \tmpdir -> bracket+		(liftIO $ mount "auto" dev tmpdir)+		(const $ liftIO $ umountLazy tmpdir)+		$ \mounted -> if mounted+			then ensureProperty $+				syncDirFiltered (filtersfor mnt) (chrootdir ++ mnt) tmpdir+			else return FailedChange++	filtersfor mnt = +		let childmnts = map (drop (length (dropTrailingPathSeparator mnt))) $+			filter (\m -> m /= mnt && addTrailingPathSeparator mnt `isPrefixOf` m)+				(catMaybes mnts)+		in concatMap (\m -> +			-- Include the child mount point, but exclude its contents.+			[ Include (Pattern m)+			, Exclude (filesUnder m)+			]) childmnts+ -- | Ensures that a disk image file of the specified size exists. --  -- If the file doesn't exist, or is too small, creates a new one, full of 0's.@@ -161,23 +194,19 @@ 			else go (M.insertWith (+) dir sz m) dir is 	subdirof parent i = not (i `equalFilePath` parent) && takeDirectory i `equalFilePath` parent --- | Gets the size to allocate for a particular mount point, given the--- map of sizes.------ A list of all mount points is provided, so that when eg calculating--- the size for /, if /boot is a mount point, its size can be subtracted. getMountSz :: (M.Map FilePath PartSize) -> [MountPoint] -> MountPoint -> Maybe PartSize getMountSz _ _ Nothing = Nothing getMountSz szm l (Just mntpt) =  	fmap (`reducePartSize` childsz) (M.lookup mntpt szm)   where-	childsz = mconcat $ catMaybes $-		map (getMountSz szm l) (filter childmntpt l)-	childmntpt Nothing = False-	childmntpt (Just d) -		| d `equalFilePath` mntpt = False-		| otherwise = mntpt `dirContains` d+	childsz = mconcat $ mapMaybe (getMountSz szm l) (filter (isChild mntpt) l) +isChild :: FilePath -> MountPoint -> Bool+isChild mntpt (Just d)+	| d `equalFilePath` mntpt = False+	| otherwise = mntpt `dirContains` d+isChild _ Nothing = False+ -- | From a location in a chroot (eg, /tmp/chroot/usr) to -- the corresponding location inside (eg, /usr). toSysDir :: FilePath -> FilePath -> FilePath@@ -191,11 +220,19 @@ defSz :: PartSize defSz = MegaBytes 128 +-- Add 2% for filesystem overhead. Rationalle for picking 2%:+-- A filesystem with 1% overhead might just sneak by as acceptable.+-- Double that just in case. Add an additional 3 mb to deal with+-- non-scaling overhead, of filesystems (eg, superblocks).+fudge :: PartSize -> PartSize+fudge (MegaBytes n) = MegaBytes (n + n `div` 100 * 2 + 3)+ -- | Specifies a mount point and a constructor for a Partition. --  -- The size that is eventually provided is the amount of space needed to  -- hold the files that appear in the directory where the partition is to be--- mounted.+-- mounted. Plus a fudge factor, since filesystems have some space+-- overhead. -- -- (Partitions that are not to be mounted (ie, LinuxSwap), or that have -- no corresponding directory in the chroot will have 128 MegaBytes@@ -235,7 +272,7 @@ extended s = adjustp s $ \p -> p { partType = Extended }  adjustp :: PartSpec -> (Partition -> Partition) -> PartSpec-adjustp (mp, p) f = (mp, \sz -> f (p sz))+adjustp (mp, p) f = (mp, f . p)  -- | The constructor for each Partition is passed the size of the files -- from the chroot that will be put in that partition.@@ -243,7 +280,7 @@ fitChrootSize tt l basesizes = (mounts, parttable)   where 	(mounts, sizers) = unzip l-	parttable = PartTable tt (map (uncurry id) (zip sizers basesizes))+	parttable = PartTable tt (zipWith id sizers basesizes)  -- | A pair of properties. The first property is satisfied within the -- chroot, and is typically used to download the boot loader.@@ -253,6 +290,7 @@ type Finalization = (Property NoInfo, Property NoInfo)  -- | Makes grub be the boot loader of the disk image.+-- TODO not implemented grubBooted :: Grub.BIOS -> Finalization grubBooted bios = (Grub.installed bios, undefined) 
src/Propellor/Property/Dns.hs view
@@ -15,6 +15,7 @@  import Propellor import Propellor.Types.Dns+import Propellor.Types.Info import Propellor.Property.File import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Ssh as Ssh@@ -27,8 +28,10 @@ import qualified Data.Set as S import Data.List --- | Primary dns server for a domain.+-- | Primary dns server for a domain, using bind. --+-- Currently, this only configures bind to serve forward DNS, not reverse DNS.+-- -- Most of the content of the zone file is configured by setting properties -- of hosts. For example, --@@ -68,17 +71,17 @@ 	zonefile = "/etc/bind/propellor/db." ++ domain  setupPrimary :: FilePath -> (FilePath -> FilePath) -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> Property HasInfo-setupPrimary zonefile mknamedconffile hosts domain soa rs = +setupPrimary zonefile mknamedconffile hosts domain soa rs = 	withwarnings baseprop 		`requires` servingZones   where 	hostmap = hostMap hosts 	-- Known hosts with hostname located in the domain. 	indomain = M.elems $ M.filterWithKey (\hn _ -> inDomain domain $ AbsDomain $ hn) hostmap-	+ 	(partialzone, zonewarnings) = genZone indomain hostmap domain soa 	baseprop = infoProperty ("dns primary for " ++ domain) satisfy-		(addNamedConf conf) []+		(mempty `addInfo` addNamedConf conf) [] 	satisfy = do 		sshfps <- concat <$> mapM (genSSHFP domain) (M.elems hostmap) 		let zone = partialzone@@ -96,7 +99,7 @@ 		, confFile = mknamedconffile zonefile 		, confMasters = [] 		, confAllowTransfer = nub $-			concatMap (\h -> hostAddresses h hosts) $+			concatMap (`hostAddresses` hosts) $ 				secondaries ++ nssecondaries 		, confLines = [] 		}@@ -152,18 +155,18 @@ signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty signedPrimary recurrance hosts domain soa rs = setup <!> cleanup   where-	setup = combineProperties ("dns primary for " ++ domain ++ " (signed)") +	setup = combineProperties ("dns primary for " ++ domain ++ " (signed)") 		(props 			& setupPrimary zonefile signedZoneFile hosts domain soa rs' 			& zoneSigned domain zonefile 			& forceZoneSigned domain zonefile `period` recurrance 		) 		`onChange` Service.reloaded "bind9"-	+ 	cleanup = cleanupPrimary zonefile domain 		`onChange` toProp (revert (zoneSigned domain zonefile)) 		`onChange` Service.reloaded "bind9"-	+ 	-- Include the public keys into the zone file. 	rs' = include PubKSK : include PubZSK : rs 	include k = (RootDomain, INCLUDE (keyFn domain k))@@ -198,7 +201,7 @@ 		{ confDomain = domain 		, confDnsServerType = Secondary 		, confFile = "db." ++ domain-		, confMasters = concatMap (\m -> hostAddresses m hosts) masters+		, confMasters = concatMap (`hostAddresses` hosts) masters 		, confAllowTransfer = [] 		, confLines = [] 		}@@ -207,7 +210,7 @@ otherServers wantedtype hosts domain = 	M.keys $ M.filter wanted $ hostMap hosts   where-	wanted h = case M.lookup domain (fromNamedConfMap $ _namedconf $ hostInfo h) of+	wanted h = case M.lookup domain (fromNamedConfMap $ getInfo $ hostInfo h) of 		Nothing -> False 		Just conf -> confDnsServerType conf == wantedtype 			&& confDomain conf == domain@@ -242,7 +245,7 @@ 	]   where 	cfgline f v = "\t" ++ f ++ " " ++ v ++ ";"-	ipblock name l = +	ipblock name l = 		[ "\t" ++ name ++ " {" ] ++ 		(map (\ip -> "\t\t" ++ fromIPAddr ip ++ ";") l) ++ 		[ "\t};" ]@@ -285,38 +288,40 @@ dValue (AbsDomain d) = d ++ "." dValue (RootDomain) = "@" -rField :: Record -> String-rField (Address (IPv4 _)) = "A"-rField (Address (IPv6 _)) = "AAAA"-rField (CNAME _) = "CNAME"-rField (MX _ _) = "MX"-rField (NS _) = "NS"-rField (TXT _) = "TXT"-rField (SRV _ _ _ _) = "SRV"-rField (SSHFP _ _ _) = "SSHFP"-rField (INCLUDE _) = "$INCLUDE"+rField :: Record -> Maybe String+rField (Address (IPv4 _)) = Just "A"+rField (Address (IPv6 _)) = Just "AAAA"+rField (CNAME _) = Just "CNAME"+rField (MX _ _) = Just "MX"+rField (NS _) = Just "NS"+rField (TXT _) = Just "TXT"+rField (SRV _ _ _ _) = Just "SRV"+rField (SSHFP _ _ _) = Just "SSHFP"+rField (INCLUDE _) = Just "$INCLUDE"+rField (PTR _) = Nothing -rValue :: Record -> String-rValue (Address (IPv4 addr)) = addr-rValue (Address (IPv6 addr)) = addr-rValue (CNAME d) = dValue d-rValue (MX pri d) = show pri ++ " " ++ dValue d-rValue (NS d) = dValue d-rValue (SRV priority weight port target) = unwords+rValue :: Record -> Maybe String+rValue (Address (IPv4 addr)) = Just addr+rValue (Address (IPv6 addr)) = Just addr+rValue (CNAME d) = Just $ dValue d+rValue (MX pri d) = Just $ show pri ++ " " ++ dValue d+rValue (NS d) = Just $ dValue d+rValue (SRV priority weight port target) = Just $ unwords 	[ show priority 	, show weight 	, show port 	, dValue target 	]-rValue (SSHFP x y s) = unwords+rValue (SSHFP x y s) = Just $ unwords 	[ show x 	, show y 	, s 	]-rValue (INCLUDE f) = f-rValue (TXT s) = [q] ++ filter (/= q) s ++ [q]+rValue (INCLUDE f) = Just f+rValue (TXT s) = Just $ [q] ++ filter (/= q) s ++ [q]   where 	q = '"'+rValue (PTR _) = Nothing  -- | Adjusts the serial number of the zone to always be larger -- than the serial number in the Zone record,@@ -374,27 +379,28 @@ -- | Generating a zone file. genZoneFile :: Zone -> String genZoneFile (Zone zdomain soa rs) = unlines $-	header : genSOA soa ++ map (genRecord zdomain) rs+	header : genSOA soa ++ mapMaybe (genRecord zdomain) rs   where 	header = com $ "BIND zone file for " ++ zdomain ++ ". Generated by propellor, do not edit." -genRecord :: Domain -> (BindDomain, Record) -> String-genRecord _ (_, record@(INCLUDE _)) = intercalate "\t"-		[ rField record-		, rValue record-		]-genRecord zdomain (domain, record) = intercalate "\t"-		[ domainHost zdomain domain-		, "IN"-		, rField record-		, rValue record-		]+genRecord :: Domain -> (BindDomain, Record) -> Maybe String+genRecord zdomain (domain, record) = case (rField record, rValue record) of+	(Nothing, _) -> Nothing+	(_, Nothing) -> Nothing+	(Just rfield, Just rvalue) -> Just $ intercalate "\t" $ case record of+		INCLUDE _ -> [ rfield, rvalue ]+		_ ->+			[ domainHost zdomain domain+			, "IN"+			, rfield+			, rvalue+			]  genSOA :: SOA -> [String]-genSOA soa = +genSOA soa = 	-- "@ IN SOA ns1.example.com. root (" 	[ intercalate "\t"-		[ dValue RootDomain +		[ dValue RootDomain 		, "IN" 		, "SOA" 		, dValue (sDomain soa)@@ -424,7 +430,7 @@ -- Does not include SSHFP records. genZone :: [Host] -> M.Map HostName Host -> Domain -> SOA -> (Zone, [WarningMessage]) genZone inzdomain hostmap zdomain soa =-	let (warnings, zhosts) = partitionEithers $ concat $ map concat+	let (warnings, zhosts) = partitionEithers $ concatMap concat 		[ map hostips inzdomain 		, map hostrecords inzdomain 		, map addcnames (M.elems hostmap)@@ -459,7 +465,7 @@ 	-- So we can just use the IPAddrs. 	addcnames :: Host -> [Either WarningMessage (BindDomain, Record)] 	addcnames h = concatMap gen $ filter (inDomain zdomain) $-		mapMaybe getCNAME $ S.toList (_dns info)+		mapMaybe getCNAME $ S.toList $ fromDnsInfo $ getInfo info 	  where 		info = hostInfo h 		gen c = case getAddresses info of@@ -467,14 +473,14 @@ 			l -> map (ret . Address) l 		  where 			ret record = Right (c, record)-	+ 	-- Adds any other DNS records for a host located in the zdomain. 	hostrecords :: Host -> [Either WarningMessage (BindDomain, Record)] 	hostrecords h = map Right l 	  where 		info = hostInfo h 		l = zip (repeat $ AbsDomain $ hostName h)-			(S.toList $ S.filter (\r -> isNothing (getIPAddr r) && isNothing (getCNAME r)) (_dns info))+			(S.toList $ S.filter (\r -> isNothing (getIPAddr r) && isNothing (getCNAME r)) (fromDnsInfo $ getInfo info))  	-- Simplifies the list of hosts. Remove duplicate entries. 	-- Also, filter out any CHAMES where the same domain has an@@ -503,13 +509,13 @@   where 	dotbase = '.':base -addNamedConf :: NamedConf -> Info-addNamedConf conf = mempty { _namedconf = NamedConfMap (M.singleton domain conf) }+addNamedConf :: NamedConf -> NamedConfMap+addNamedConf conf = NamedConfMap (M.singleton domain conf)   where 	domain = confDomain conf  getNamedConf :: Propellor (M.Map Domain NamedConf)-getNamedConf = asks $ fromNamedConfMap . _namedconf . hostInfo+getNamedConf = asks $ fromNamedConfMap . getInfo . hostInfo  -- | Generates SSHFP records for hosts in the domain (or with CNAMES -- in the domain) that have configured ssh public keys.@@ -522,7 +528,7 @@ 	gen = liftIO . mapM genSSHFP' . M.elems . fromMaybe M.empty 	mk r = mapMaybe (\d -> if inDomain domain d then Just (d, r) else Nothing) 		(AbsDomain hostname : cnames)-	cnames = mapMaybe getCNAME $ S.toList $ _dns info+	cnames = mapMaybe getCNAME $ S.toList $ fromDnsInfo $ getInfo info 	hostname = hostName h 	info = hostInfo h 
src/Propellor/Property/Docker.hs view
@@ -49,6 +49,7 @@ import Propellor.Types.Docker import Propellor.Types.Container import Propellor.Types.CmdLine+import Propellor.Types.Info import qualified Propellor.Property.File as File import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Cmd as Cmd@@ -74,7 +75,7 @@   where 	prop = withPrivData src anyContext $ \getcfg -> 		property "docker configured" $ getcfg $ \cfg -> ensureProperty $ -			"/root/.dockercfg" `File.hasContent` (lines cfg)+			"/root/.dockercfg" `File.hasContent` privDataLines cfg 	src = PrivDataSourceFileFromCommand DockerAuthentication 		"/root/.dockercfg" "docker login" @@ -186,7 +187,7 @@   where 	runparams = map (\(DockerRunParam mkparam) -> mkparam hn) 		(_dockerRunParams info)-	info = _dockerinfo $ hostInfo h'+	info = getInfo $ hostInfo h' 	h' = h 		-- Restart by default so container comes up on 		-- boot or when docker is upgraded.@@ -212,7 +213,7 @@   where 	gccontainers = property "docker containers garbage collected" $ 		liftIO $ report <$> (mapM removeContainer =<< listContainers AllContainers)-	gcimages = property "docker images garbage collected" $ do+	gcimages = property "docker images garbage collected" $ 		liftIO $ report <$> (mapM removeImage =<< listImages)  -- | Tweaks a container to work well with docker.@@ -470,8 +471,7 @@  	restartcontainer = do 		oldimage <- liftIO $-			fromMaybe (toImageID image) . fmap toImageID <$>-				commitContainer cid+			maybe (toImageID image) toImageID <$> commitContainer cid 		void $ liftIO $ removeContainer cid 		go oldimage @@ -572,7 +572,7 @@ 	Nothing -> errorMessage "bad container id" 	Just cid -> case findHostNoAlias hostlist hn of 		Nothing -> errorMessage ("cannot find host " ++ hn)-		Just parenthost -> case M.lookup (containerName cid) (_dockerContainers $ _dockerinfo $ hostInfo parenthost) of+		Just parenthost -> case M.lookup (containerName cid) (_dockerContainers $ getInfo $ hostInfo parenthost) of 			Nothing -> errorMessage ("cannot find container " ++ containerName cid ++ " docked on host " ++ hn) 			Just h -> go cid h   where@@ -630,8 +630,8 @@ -- | Only lists propellor managed containers. listContainers :: ContainerFilter -> IO [ContainerId] listContainers status = -	catMaybes . map toContainerId . concat . map (split ",")-		. catMaybes . map (lastMaybe . words) . lines+	mapMaybe toContainerId . concatMap (split ",")+		. mapMaybe (lastMaybe . words) . lines 		<$> readProcess dockercmd ps   where 	ps@@ -643,17 +643,17 @@ listImages = map ImageUID . lines <$> readProcess dockercmd ["images", "--all", "--quiet"]  runProp :: String -> RunParam -> Property HasInfo-runProp field val = pureInfoProperty (param) $ dockerInfo $+runProp field val = pureInfoProperty (param) $ 	mempty { _dockerRunParams = [DockerRunParam (\_ -> "--"++param)] }   where 	param = field++"="++val  genProp :: String -> (HostName -> RunParam) -> Property HasInfo-genProp field mkval = pureInfoProperty field $ dockerInfo $+genProp field mkval = pureInfoProperty field $ 	mempty { _dockerRunParams = [DockerRunParam (\hn -> "--"++field++"=" ++ mkval hn)] } -dockerInfo :: DockerInfo Host -> Info-dockerInfo i = mempty { _dockerinfo = i }+dockerInfo :: DockerInfo -> Info+dockerInfo i = mempty `addInfo` i  -- | The ContainerIdent of a container is written to -- </.propellor-ident> inside it. This can be checked to see if
src/Propellor/Property/File.hs view
@@ -10,9 +10,17 @@  -- | Replaces all the content of a file. hasContent :: FilePath -> [Line] -> Property NoInfo-f `hasContent` newcontent = fileProperty ("replace " ++ f)+f `hasContent` newcontent = fileProperty+	("replace " ++ f) 	(\_oldcontent -> newcontent) f +-- | Replaces all the content of a file, ensuring that its modes do not+-- allow it to be read or written by anyone other than the current user+hasContentProtected :: FilePath -> [Line] -> Property NoInfo+f `hasContentProtected` newcontent = fileProperty' writeFileProtected +	("replace " ++ f)+	(\_oldcontent -> newcontent) f+ -- | Ensures a file has contents that comes from PrivData. -- -- The file's permissions are preserved if the file already existed.@@ -40,7 +48,7 @@ 	withPrivData source context $ \getcontent ->  		property desc $ getcontent $ \privcontent ->  			ensureProperty $ fileProperty' writer desc-				(\_oldcontent -> lines privcontent) f+				(\_oldcontent -> privDataLines privcontent) f   where 	desc = "privcontent " ++ f @@ -103,5 +111,5 @@ -- | Ensures that a file/dir has the specfied mode. mode :: FilePath -> FileMode -> Property NoInfo mode f v = property (f ++ " mode " ++ show v) $ do-	liftIO $ modifyFileMode f (\_old -> v)+	liftIO $ modifyFileMode f (const v) 	noChange
src/Propellor/Property/Gpg.hs view
@@ -33,7 +33,7 @@ 			withHandle StdinHandle createProcessSuccess 				(proc "su" ["-c", "gpg --import", u]) $ \h -> do 					fileEncoding h-					hPutStr h key+					hPutStr h (unlines (privDataLines key)) 					hClose h 	src = PrivDataSource GpgKey "Either a gpg public key, exported with gpg --export -a, or a gpg private key, exported with gpg --export-secret-key -a" 
src/Propellor/Property/Reboot.hs view
@@ -8,7 +8,7 @@  -- | Schedules a reboot at the end of the current propellor run. ----- The Result code of the endire propellor run can be checked;+-- The `Result` code of the entire propellor run can be checked; -- the reboot proceeds only if the function returns True. -- -- The reboot can be forced to run, which bypasses the init system. Useful
+ src/Propellor/Property/Rsync.hs view
@@ -0,0 +1,59 @@+module Propellor.Property.Rsync where++import Propellor+import qualified Propellor.Property.Apt as Apt++type Src = FilePath+type Dest = FilePath++class RsyncParam p where+	toRsync :: p -> String++-- | A pattern that matches all files under a directory, but does not+-- match the directory itself.+filesUnder :: FilePath -> Pattern+filesUnder d = Pattern (d ++ "/*")++-- | Ensures that the Dest directory exists and has identical contents as+-- the Src directory.+syncDir :: Src -> Dest -> Property NoInfo+syncDir = syncDirFiltered []++data Filter +	= Include Pattern+	| Exclude Pattern++instance RsyncParam Filter where+	toRsync (Include (Pattern p)) = "--include=" ++ p+	toRsync (Exclude (Pattern p)) = "--exclude=" ++ p++-- | A pattern to match against files that rsync is going to transfer.+--+-- See "INCLUDE/EXCLUDE PATTERN RULES" in the rsync(1) man page.+--+-- For example, Pattern "/foo/*" matches all files under the "foo"+-- directory, relative to the 'Src' that rsync is acting on.+newtype Pattern = Pattern String++-- | Like syncDir, but avoids copying anything that the filter list+-- excludes. Anything that's filtered out will be deleted from Dest.+--+-- Rsync checks each name to be transferred against its list of Filter+-- rules, and the first matching one is acted on. If no matching rule+-- is found, the file is processed.+syncDirFiltered :: [Filter] -> Src -> Dest -> Property NoInfo+syncDirFiltered filters src dest = rsync $+	[ "-av"+	-- Add trailing '/' to get rsync to sync the Dest directory,+	-- rather than a subdir inside it, which it will do without a+	-- trailing '/'.+	, addTrailingPathSeparator src+	, addTrailingPathSeparator dest+	, "--delete"+	, "--delete-excluded"+	, "--quiet"+	] ++ map toRsync filters++rsync :: [String] -> Property NoInfo+rsync ps = cmdProperty "rsync" ps+	`requires` Apt.installed ["rsync"]
src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs view
@@ -39,14 +39,15 @@ 	-- password used to upload the built image. 	rsyncpassword = withPrivData (Password builduser) context $ \getpw -> 		property "rsync password" $ getpw $ \pw -> do-			oldpw <- liftIO $ catchDefaultIO "" $+			have <- liftIO $ catchDefaultIO "" $ 				readFileStrict pwfile-			if pw /= oldpw-				then makeChange $ writeFile pwfile pw+			let want = privDataVal pw+			if want /= have+				then makeChange $ writeFile pwfile want 				else noChange -tree :: Architecture -> Property HasInfo-tree buildarch = combineProperties "gitannexbuilder tree" $ props+tree :: Architecture -> Flavor -> Property HasInfo+tree buildarch flavor = combineProperties "gitannexbuilder tree" $ props 	& Apt.installed ["git"] 	& File.dirExists gitbuilderdir 	& File.ownerGroup gitbuilderdir (User builduser) (Group builduser)@@ -57,7 +58,7 @@ 		userScriptProperty (User builduser) 			[ "git clone git://git.kitenet.net/gitannexbuilder " ++ gitbuilderdir 			, "cd " ++ gitbuilderdir-			, "git checkout " ++ buildarch+			, "git checkout " ++ buildarch ++ fromMaybe "" flavor 			] 			`describe` "gitbuilder setup" 	builddircloned = check (not <$> doesDirectoryExist builddir) $ userScriptProperty (User builduser)@@ -96,29 +97,31 @@ 		go = userScriptProperty (User builduser) ["cabal update && cabal install git-annex --only-dependencies || true"] 		cabalupdated = homedir </> ".cabal" </> "packages" </> "hackage.haskell.org" </> "00-index.cache" -autoBuilderContainer :: (System -> Property HasInfo) -> System -> Times -> TimeOut -> Systemd.Container-autoBuilderContainer mkprop osver@(System _ arch) crontime timeout =+autoBuilderContainer :: (System -> Flavor -> Property HasInfo) -> System -> Flavor -> Times -> TimeOut -> Systemd.Container+autoBuilderContainer mkprop osver@(System _ arch) flavor crontime timeout = 	Systemd.container name bootstrap-		& mkprop osver+		& mkprop osver flavor 		& buildDepsApt 		& autobuilder arch crontime timeout   where-	name = arch ++ "-git-annex-builder"+	name = arch ++ fromMaybe "" flavor ++ "-git-annex-builder" 	bootstrap = Chroot.debootstrapped osver mempty -standardAutoBuilder :: System -> Property HasInfo-standardAutoBuilder osver@(System _ arch) =+type Flavor = Maybe String++standardAutoBuilder :: System -> Flavor -> Property HasInfo+standardAutoBuilder osver@(System _ arch) flavor = 	propertyList "standard git-annex autobuilder" $ props 		& os osver 		& Apt.stdSourcesList 		& Apt.unattendedUpgrades 		& User.accountFor (User builduser)-		& tree arch+		& tree arch flavor -armAutoBuilder :: System -> Property HasInfo-armAutoBuilder osver = +armAutoBuilder :: System -> Flavor -> Property HasInfo+armAutoBuilder osver flavor =  	propertyList "arm git-annex autobuilder" $ props-		& standardAutoBuilder osver+		& standardAutoBuilder osver flavor 		& buildDepsNoHaskellLibs 		-- Works around ghc crash with parallel builds on arm. 		& (homedir </> ".cabal" </> "config")@@ -129,7 +132,7 @@  androidAutoBuilderContainer :: Times -> TimeOut -> Systemd.Container androidAutoBuilderContainer crontimes timeout =-	androidContainer "android-git-annex-builder" (tree "android") builddir+	androidContainer "android-git-annex-builder" (tree "android" Nothing) builddir 		& Apt.unattendedUpgrades 		& buildDepsNoHaskellLibs 		& autobuilder "android" crontimes timeout
src/Propellor/Property/SiteSpecific/IABak.hs view
@@ -103,4 +103,4 @@ 	graphiteCSRF = withPrivData (Password "csrf-token") (Context "iabak.archiveteam.org") $ 		\gettoken -> property "graphite-web CSRF token" $ 			gettoken $ \token -> ensureProperty $ File.containsLine-				"/etc/graphite/local_settings.py" ("SECRET_KEY = '"++ token ++"'")+				"/etc/graphite/local_settings.py" ("SECRET_KEY = '"++ privDataVal token ++"'")
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -520,6 +520,9 @@ 		`onChange` Service.restarted "amavisd-milter" 		`describe` "amavisd-milter configured for postfix" 	& Apt.serviceInstalledRunning "clamav-freshclam"+	-- Workaround https://bugs.debian.org/569150+	& Cron.niceJob "amavis-expire" Cron.Daily (User "root") "/"+		"find /var/lib/amavis/virusmails/ -type f -ctime +7 -delete"  	& dkimInstalled 
src/Propellor/Property/Ssh.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DeriveDataTypeable #-}+ module Propellor.Property.Ssh ( 	PubKeyText, 	sshdConfig,@@ -27,6 +29,7 @@ import qualified Propellor.Property.File as File import qualified Propellor.Property.Service as Service import Propellor.Property.User+import Propellor.Types.Info import Utility.FileMode  import System.PosixCompat@@ -119,7 +122,7 @@ -- -- The corresponding private keys come from the privdata. ----- Any host keysthat are not in the list are removed from the host.+-- Any host keys that are not in the list are removed from the host. hostKeys :: IsContext c => c -> [(SshKeyType, PubKeyText)] -> Property HasInfo hostKeys ctx l = propertyList desc $ catMaybes $ 	map (\(t, pub) -> Just $ hostKey ctx t pub) l ++ [cleanup]@@ -144,22 +147,29 @@ hostKey :: IsContext c => c -> SshKeyType -> PubKeyText -> Property HasInfo hostKey context keytype pub = combineProperties desc 	[ pubKey keytype pub-	, toProp $ property desc $ install writeFile True pub+	, toProp $ property desc $ install writeFile True (lines pub) 	, withPrivData (keysrc "" (SshPrivKey keytype "")) context $ \getkey ->-		property desc $ getkey $ install writeFileProtected False+		property desc $ getkey $+			install writeFileProtected False . privDataLines 	] 	`onChange` restarted   where 	desc = "ssh host key configured (" ++ fromKeyType keytype ++ ")"-	install writer ispub key = do+	install writer ispub keylines = do 		let f = keyFile keytype ispub-		s <- liftIO $ catchDefaultIO "" $ readFileStrict f-		if s == key+		have <- liftIO $ catchDefaultIO "" $ readFileStrict f+		let want = keyFileContent keylines+		if have == want 			then noChange-			else makeChange $ writer f key+			else makeChange $ writer f want 	keysrc ext field = PrivDataSourceFileFromCommand field ("sshkey"++ext) 		("ssh-keygen -t " ++ sshKeyTypeParam keytype ++ " -f sshkey") +-- Make sure that there is a newline at the end;+-- ssh requires this for some types of private keys.+keyFileContent :: [String] -> String+keyFileContent keylines = unlines (keylines ++ [""])+ keyFile :: SshKeyType -> Bool -> FilePath keyFile keytype ispub = "/etc/ssh/ssh_host_" ++ fromKeyType keytype ++ "_key" ++ ext   where@@ -169,12 +179,25 @@ -- configure the host to use it. Normally this does not need to be used; -- use 'hostKey' instead. pubKey :: SshKeyType -> PubKeyText -> Property HasInfo-pubKey t k = pureInfoProperty ("ssh pubkey known") $-	mempty { _sshPubKey = M.singleton t k }+pubKey t = pureInfoProperty "ssh pubkey known" . SshPubKeyInfo . M.singleton t -getPubKey :: Propellor (M.Map SshKeyType String)-getPubKey = asks (_sshPubKey . hostInfo)+getPubKey :: Propellor (M.Map SshKeyType PubKeyText)+getPubKey = fromSshPubKeyInfo <$> askInfo +newtype SshPubKeyInfo = SshPubKeyInfo +	{ fromSshPubKeyInfo :: M.Map SshKeyType PubKeyText }+	deriving (Eq, Ord, Typeable)++instance IsInfo SshPubKeyInfo where+	propigateInfo _ = False++instance Monoid SshPubKeyInfo where+	mempty = SshPubKeyInfo M.empty+	mappend (SshPubKeyInfo old) (SshPubKeyInfo new) = +		-- new first because union prefers values from the first+		-- parameter when there is a duplicate key+		SshPubKeyInfo (new `M.union` old)+ -- | Sets up a user with a ssh private key and public key pair from the -- PrivData. --@@ -205,7 +228,7 @@ 			, ensureProperties 				[ property desc $ makeChange $ do 					createDirectoryIfMissing True (takeDirectory f)-					writer f key+					writer f (keyFileContent (privDataLines key)) 				, File.ownerGroup f user (userGroup user) 				, File.ownerGroup (takeDirectory f) user (userGroup user) 				]@@ -216,6 +239,8 @@ 			return $ home </> ".ssh" </> "id_" ++ fromKeyType keytype ++ ext 		Just f -> return $ f ++ ext ++ fromKeyType :: SshKeyType -> String fromKeyType SshRsa = "rsa" fromKeyType SshDsa = "dsa"@@ -251,7 +276,7 @@ 		f <- liftIO $ dotFile "authorized_keys" user 		liftIO $ do 			createDirectoryIfMissing True (takeDirectory f)-			writeFileProtected f v+			writeFileProtected f (keyFileContent (privDataLines v)) 		ensureProperties  			[ File.ownerGroup f user (userGroup user) 			, File.ownerGroup (takeDirectory f) user (userGroup user)
src/Propellor/Property/Systemd.hs view
@@ -43,6 +43,7 @@ import Propellor import Propellor.Types.Chroot import Propellor.Types.Container+import Propellor.Types.Info import qualified Propellor.Property.Chroot as Chroot import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.File as File@@ -209,7 +210,7 @@   where 	p = enterScript c 		`before` chrootprovisioned-		`before` nspawnService c (_chrootCfg $ _chrootinfo $ hostInfo h)+		`before` nspawnService c (_chrootCfg $ getInfo $ hostInfo h) 		`before` containerprovisioned  	-- Chroot provisioning is run in systemd-only mode,@@ -328,7 +329,7 @@ containerCfg p = RevertableProperty (mk True) (mk False)   where 	mk b = pureInfoProperty ("container configuration " ++ (if b then "" else "without ") ++ p') $-		mempty { _chrootinfo = mempty { _chrootCfg = SystemdNspawnCfg [(p', b)] } }+		mempty { _chrootCfg = SystemdNspawnCfg [(p', b)] } 	p' = case p of 		('-':_) -> p 		_ -> "--" ++ p
src/Propellor/Property/Tor.hs view
@@ -134,12 +134,12 @@ 	desc = unwords ["hidden service data available in", varLib </> hn] 	installonion f = withPrivData (PrivFile $ varLib </> hn </> f) context $ \getcontent -> 		property desc $ getcontent $ install $ varLib </> hn </> f-	install f content = ifM (liftIO $ doesFileExist f)+	install f privcontent = ifM (liftIO $ doesFileExist f) 		( noChange 		, ensureProperties 			[ property desc $ makeChange $ do 				createDirectoryIfMissing True (takeDirectory f)-				writeFileProtected f content+				writeFileProtected f (unlines (privDataLines privcontent)) 			, File.mode (takeDirectory f) $ combineModes 				[ownerReadMode, ownerWriteMode, ownerExecuteMode] 			, File.ownerGroup (takeDirectory f) user (userGroup user)
+ src/Propellor/Property/Unbound.hs view
@@ -0,0 +1,132 @@+-- | Properties for the Unbound caching DNS server++module Propellor.Property.Unbound+	( installed+	, restarted+	, reloaded+	, cachingDnsServer+	) where++import Propellor+import Propellor.Property.File+import qualified Propellor.Property.Apt as Apt+import qualified Propellor.Property.Service as Service++import Data.List (find)+++type ConfSection = String++type UnboundSetting = (UnboundKey, UnboundValue)++type UnboundSection = (ConfSection, [UnboundSetting])++type UnboundZone = (BindDomain, ZoneType)++type UnboundHost = (BindDomain, Record)++type UnboundKey = String++type UnboundValue = String++type ZoneType = String++installed :: Property NoInfo+installed = Apt.installed ["unbound"]++restarted :: Property NoInfo+restarted = Service.restarted "unbound"++reloaded :: Property NoInfo+reloaded = Service.reloaded "unbound"++dValue :: BindDomain -> String+dValue (RelDomain d) = d+dValue (AbsDomain d) = d ++ "."+dValue (RootDomain) = "@"++sectionHeader :: ConfSection -> String+sectionHeader header = header ++ ":"++config :: FilePath+config = "/etc/unbound/unbound.conf.d/propellor.conf"++-- | Provided a [UnboundSection], a [UnboundZone] and a [UnboundHost],+-- cachingDnsServer ensure unbound is configured accordingly.+--+-- Example property:+--+-- > cachingDnsServer+-- >      [ ("remote-control", [("control-enable", "no")]+-- >      , ("server",+-- >      	[ ("interface", "0.0.0.0")+-- >      	, ("access-control", "192.168.1.0/24 allow")+-- >      	, ("do-tcp", "no")+-- >      	])+-- >      [ (AbsDomain "example.com", "transparent")+-- >      , (AbsDomain $ reverseIP $ IPv4 "192.168.1", "static")+-- >      ]+-- >      [ (AbsDomain "example.com", Address $ IPv4 "192.168.1.2")+-- >      , (AbsDomain "myhost.example.com", Address $ IPv4 "192.168.1.2")+-- >      , (AbsDomain "myrouter.example.com", Address $ IPv4 "192.168.1.1")+-- >      , (AbsDomain "www.example.com", Address $ IPv4 "192.168.1.2")+-- >      , (AbsDomain "example.com", MX 10 "mail.example.com")+-- >      , (AbsDomain "mylaptop.example.com", Address $ IPv4 "192.168.1.2")+-- >      -- ^ connected via ethernet+-- >      , (AbsDomain "mywifi.example.com", Address $ IPv4 "192.168.2.1")+-- >      , (AbsDomain "mylaptop.example.com", Address $ IPv4 "192.168.2.2")+-- >      -- ^ connected via wifi, use round robin+-- >      , (AbsDomain "myhost.example.com", PTR $ reverseIP $ IPv4 "192.168.1.2")+-- >      , (AbsDomain "myrouter.example.com", PTR $ reverseIP $ IPv4 "192.168.1.1")+-- >      , (AbsDomain "mylaptop.example.com", PTR $ reverseIP $ IPv4 "192.168.1.2")+-- >      ]+cachingDnsServer :: [UnboundSection] -> [UnboundZone] -> [UnboundHost] -> Property NoInfo+cachingDnsServer sections zones hosts =+	config `hasContent` (comment : otherSections ++ serverSection)+	`onChange` restarted+  where+	comment = "# deployed with propellor, do not modify"+	serverSection = genSection (fromMaybe ("server", []) $ find ((== "server") . fst) sections)+		++ map genZone zones+		++ map (uncurry genRecord') hosts+	otherSections = foldr ((++) . genSection) [] $ filter ((/= "server") . fst) sections++genSection :: UnboundSection -> [Line]+genSection (section, settings) = sectionHeader section : map genSetting settings++genSetting :: UnboundSetting -> Line+genSetting (key, value) = "    " ++ key ++ ": " ++ value++genZone :: UnboundZone -> Line+genZone (dom, zt) = "    local-zone: \"" ++ dValue dom ++ "\" " ++ zt++genRecord' :: BindDomain -> Record -> Line+genRecord' dom r = "    local-data: \"" ++ fromMaybe "" (genRecord dom r) ++ "\""++genRecord :: BindDomain -> Record -> Maybe String+genRecord dom (Address addr) = Just $ genAddressNoTtl dom addr+genRecord dom (MX priority dest) = Just $ genMX dom priority dest+genRecord dom (PTR revip) = Just $ genPTR dom revip+genRecord _ (CNAME _) = Nothing+genRecord _ (NS _) = Nothing+genRecord _ (TXT _) = Nothing+genRecord _ (SRV _ _ _ _) = Nothing+genRecord _ (SSHFP _ _ _) = Nothing+genRecord _ (INCLUDE _) = Nothing++genAddressNoTtl :: BindDomain -> IPAddr -> String+genAddressNoTtl dom = genAddress dom Nothing++genAddress :: BindDomain -> Maybe Int -> IPAddr -> String+genAddress dom ttl addr = case addr of+	IPv4 _ -> genAddress' "A" dom ttl addr+	IPv6 _ -> genAddress' "AAAA" dom ttl addr++genAddress' :: String -> BindDomain -> Maybe Int -> IPAddr -> String+genAddress' recordtype dom ttl addr = dValue dom ++ " " ++ maybe "" (\ttl' -> show ttl' ++ " ") ttl ++ "IN " ++ recordtype ++ " " ++ fromIPAddr addr++genMX :: BindDomain -> Int -> BindDomain -> String+genMX dom priority dest = dValue dom ++ " " ++ "MX" ++ " " ++ show priority ++ " " ++ dValue dest++genPTR :: BindDomain -> ReverseIP -> String+genPTR dom revip = revip ++ ". " ++ "PTR" ++ " " ++ dValue dom
src/Propellor/Property/User.hs view
@@ -58,8 +58,8 @@ setPassword :: (((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Propellor Result setPassword getpassword = getpassword $ go   where-	go (Password user, password) = set user password []-	go (CryptPassword user, hash) = set user hash ["--encrypted"]+	go (Password user, password) = set user (privDataVal password) []+	go (CryptPassword user, hash) = set user (privDataVal hash) ["--encrypted"] 	go (f, _) = error $ "Unexpected type of privdata: " ++ show f  	set user v ps = makeChange $ withHandle StdinHandle createProcessSuccess
src/Propellor/Shim.hs view
@@ -77,7 +77,7 @@ installFile top f = do 	createDirectoryIfMissing True destdir 	nukeFile dest-	createLink f dest `catchIO` (const copy)+	createLink f dest `catchIO` const copy   where 	copy = void $ boolSystem "cp" [Param "-a", Param f, Param dest] 	destdir = inTop top $ takeDirectory f
src/Propellor/Spin.hs view
@@ -24,6 +24,7 @@ import Propellor.Gpg import Propellor.Bootstrap import Propellor.Types.CmdLine+import Propellor.Types.Info import qualified Propellor.Shim as Shim import Utility.FileMode import Utility.SafeCommand@@ -58,7 +59,7 @@  	-- And now we can run it. 	unlessM (boolSystem "ssh" (map Param $ cacheparams ++ ["-t", sshtarget, shellWrap runcmd])) $-		error $ "remote propellor failed"+		error "remote propellor failed"   where 	hn = fromMaybe target relay @@ -126,7 +127,7 @@ 					return ip  	configips = map fromIPAddr $ mapMaybe getIPAddr $-		S.toList $ _dns $ hostInfo hst+		S.toList $ fromDnsInfo $ getInfo $ hostInfo hst  -- Update the privdata, repo url, and git repo over the ssh -- connection, talking to the user's local propellor instance which is@@ -183,7 +184,7 @@ 		let loop = go (toh, fromh) 		let restart = updateServer hn relay hst connect haveprecompiled 		let done = return ()-		v <- (maybe Nothing readish <$> getMarked fromh statusMarker)+		v <- maybe Nothing readish <$> getMarked fromh statusMarker 		case v of 			(Just NeedRepoUrl) -> do 				sendRepoUrl toh@@ -262,7 +263,7 @@ -- This should be reasonably portable, as long as the remote host has the -- same architecture as the build host. sendPrecompiled :: HostName -> IO ()-sendPrecompiled hn = void $ actionMessage ("Uploading locally compiled propellor as a last resort") $ do+sendPrecompiled hn = void $ actionMessage "Uploading locally compiled propellor as a last resort" $ 	bracket getWorkingDirectory changeWorkingDirectory $ \_ -> 		withTmpDir "propellor" go   where
src/Propellor/Types.hs view
@@ -6,11 +6,13 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveDataTypeable #-}  module Propellor.Types 	( Host(..) 	, Desc 	, Property+	, Info 	, HasInfo 	, NoInfo 	, CInfo@@ -27,7 +29,6 @@ 	, CombinedType 	, before 	, combineWith-	, Info(..) 	, Propellor(..) 	, EndAction(..) 	, module Propellor.Types.OS@@ -41,18 +42,12 @@ import Control.Applicative import "mtl" Control.Monad.RWS.Strict import Control.Monad.Catch-import qualified Data.Set as S-import qualified Data.Map as M+import Data.Typeable +import Propellor.Types.Info import Propellor.Types.OS-import Propellor.Types.Chroot import Propellor.Types.Dns-import Propellor.Types.Docker-import Propellor.Types.PrivData-import Propellor.Types.Empty-import Propellor.Types.Val import Propellor.Types.Result-import qualified Propellor.Types.Dns as Dns  -- | Everything Propellor knows about a system: Its hostname, -- properties and their collected info.@@ -61,7 +56,7 @@ 	, hostProperties :: [Property HasInfo] 	, hostInfo :: Info 	}-	deriving (Show)+	deriving (Show, Typeable)  -- | Propellor's monad provides read-only access to info about the host -- it's running on, and a writer to accumulate EndActions.@@ -224,7 +219,7 @@ -- that ensures the first, and if the first succeeds, ensures the second. -- The property uses the description of the first property. before :: (IsProp x, Combines y x, IsProp (CombinedType y x)) => x -> y -> CombinedType y x-before x y = (y `requires` x) `describe` (getDesc x)+before x y = (y `requires` x) `describe` getDesc x  -- | Combines together two properties, yielding a property that -- has the description and info of the first, and that has the second@@ -269,41 +264,3 @@ 			(x1 `requires` y1) 			-- when reverting, run actions in reverse order 			(y2 `requires` x2)---- | Information about a host.-data Info = Info-	{ _os :: Val System-	, _privData :: S.Set (PrivDataField, Maybe PrivDataSourceDesc, HostContext)-	, _sshPubKey :: M.Map SshKeyType String-	, _aliases :: S.Set HostName-	, _dns :: S.Set Dns.Record-	, _namedconf :: Dns.NamedConfMap-	, _dockerinfo :: DockerInfo Host-	, _chrootinfo :: ChrootInfo Host-	}-	deriving (Show)--instance Monoid Info where-	mempty = Info mempty mempty mempty mempty mempty mempty mempty mempty-	mappend old new = Info-		{ _os = _os old <> _os new-		, _privData = _privData old <> _privData new-		, _sshPubKey = _sshPubKey new `M.union` _sshPubKey old-		, _aliases = _aliases old <> _aliases new-		, _dns = _dns old <> _dns new-		, _namedconf = _namedconf old <> _namedconf new-		, _dockerinfo = _dockerinfo old <> _dockerinfo new-		, _chrootinfo = _chrootinfo old <> _chrootinfo new-		}--instance Empty Info where-	isEmpty i = and-		[ isEmpty (_os i)-		, isEmpty (_privData i)-		, isEmpty (_sshPubKey i)-		, isEmpty (_aliases i)-		, isEmpty (_dns i)-		, isEmpty (_namedconf i)-		, isEmpty (_dockerinfo i)-		, isEmpty (_chrootinfo i)-		]
src/Propellor/Types/Chroot.hs view
@@ -1,23 +1,31 @@+{-# LANGUAGE DeriveDataTypeable #-}+ module Propellor.Types.Chroot where +import Propellor.Types+import Propellor.Types.Empty+import Propellor.Types.Info+ import Data.Monoid import qualified Data.Map as M-import Propellor.Types.Empty -data ChrootInfo host = ChrootInfo-	{ _chroots :: M.Map FilePath host+data ChrootInfo = ChrootInfo+	{ _chroots :: M.Map FilePath Host 	, _chrootCfg :: ChrootCfg 	}-	deriving (Show)+	deriving (Show, Typeable) -instance Monoid (ChrootInfo host) where+instance IsInfo ChrootInfo where+	propigateInfo _ = False++instance Monoid ChrootInfo where 	mempty = ChrootInfo mempty mempty 	mappend old new = ChrootInfo 		{ _chroots = M.union (_chroots old) (_chroots new) 		, _chrootCfg = _chrootCfg old <> _chrootCfg new 		} -instance Empty (ChrootInfo host) where+instance Empty ChrootInfo where 	isEmpty i = and 		[ isEmpty (_chroots i) 		, isEmpty (_chrootCfg i)
src/Propellor/Types/CmdLine.hs view
@@ -15,6 +15,7 @@ 	| Edit PrivDataField Context 	| ListFields 	| AddKey String+	| RmKey String 	| Merge 	| Serialized CmdLine 	| Continue CmdLine
src/Propellor/Types/Container.hs view
@@ -17,12 +17,12 @@ -- For example, @Port 8080 -<- Port 80@ means that port 8080 on the host -- is bound to port 80 from the container. (-<-) :: (hostv ~ v, containerv ~ v) => hostv -> containerv -> Bound v-(-<-) hostv containerv = Bound hostv containerv+(-<-) = Bound  -- | Flipped version of -<- with the container value first and host value -- second. (->-) :: (containerv ~ v, hostv ~ v) => hostv -> containerv -> Bound v-(->-) containerv hostv = Bound hostv containerv+(->-) = flip (-<-)  -- | Create a Bound value, that is the same on both the host and container. same :: v -> Bound v
src/Propellor/Types/Dns.hs view
@@ -1,11 +1,17 @@+{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-}+ module Propellor.Types.Dns where  import Propellor.Types.OS (HostName) import Propellor.Types.Empty+import Propellor.Types.Info  import Data.Word import Data.Monoid import qualified Data.Map as M+import qualified Data.Set as S+import Data.List+import Data.String.Utils (split, replace)  type Domain = String @@ -16,6 +22,29 @@ fromIPAddr (IPv4 addr) = addr fromIPAddr (IPv6 addr) = addr +newtype AliasesInfo = AliasesInfo (S.Set HostName)+	deriving (Show, Eq, Ord, Monoid, Typeable)++instance IsInfo AliasesInfo where+	propigateInfo _ = False++toAliasesInfo :: [HostName] -> AliasesInfo+toAliasesInfo l = AliasesInfo (S.fromList l)++fromAliasesInfo :: AliasesInfo -> [HostName]+fromAliasesInfo (AliasesInfo s) = S.toList s++newtype DnsInfo = DnsInfo { fromDnsInfo :: S.Set Record }+	deriving (Show, Eq, Ord, Monoid, Typeable)++toDnsInfo :: S.Set Record -> DnsInfo+toDnsInfo = DnsInfo++-- | DNS Info is propigated, so that eg, aliases of a container+-- are reflected in the dns for the host where it runs.+instance IsInfo DnsInfo where+	propigateInfo _ = True+ -- | Represents a bind 9 named.conf file. data NamedConf = NamedConf 	{ confDomain :: Domain@@ -64,8 +93,34 @@ 	| SRV Word16 Word16 Word16 BindDomain 	| SSHFP Int Int String 	| INCLUDE FilePath-	deriving (Read, Show, Eq, Ord)+	| PTR ReverseIP+	deriving (Read, Show, Eq, Ord, Typeable) +-- | An in-addr.arpa record corresponding to an IPAddr.+type ReverseIP = String++reverseIP :: IPAddr -> ReverseIP+reverseIP (IPv4 addr) = intercalate "." (reverse $ split "." addr) ++ ".in-addr.arpa"+reverseIP addr@(IPv6 _) = reverse (intersperse '.' $ replace ":" "" $ fromIPAddr $ canonicalIP addr) ++ ".ip6.arpa"++-- | Converts an IP address (particularly IPv6) to canonical, fully+-- expanded form.+canonicalIP :: IPAddr -> IPAddr+canonicalIP (IPv4 addr) = IPv4 addr+canonicalIP (IPv6 addr) = IPv6 $ intercalate ":" $ map canonicalGroup $ split ":" $ replaceImplicitGroups addr+  where+	canonicalGroup g+		| l <= 4    = replicate (4 - l) '0' ++ g+		| otherwise = error $ "IPv6 group " ++ g ++ "as more than 4 hex digits"+	  where+		l = length g+	emptyGroups n = iterate (++ ":") "" !! n+	numberOfImplicitGroups a = 8 - length (split ":" $ replace "::" "" a)+	replaceImplicitGroups a = concat $ aux $ split "::" a+	  where+		aux [] = []+		aux (x : xs) = x : emptyGroups (numberOfImplicitGroups a) : xs+ getIPAddr :: Record -> Maybe IPAddr getIPAddr (Address addr) = Just addr getIPAddr _ = Nothing@@ -97,7 +152,10 @@ domainHostName RootDomain = Nothing  newtype NamedConfMap = NamedConfMap (M.Map Domain NamedConf)-	deriving (Eq, Ord, Show)+	deriving (Eq, Ord, Show, Typeable)++instance IsInfo NamedConfMap where+	propigateInfo _ = False  -- | Adding a Master NamedConf stanza for a particulr domain always -- overrides an existing Secondary stanza for that domain, while a
src/Propellor/Types/Docker.hs view
@@ -1,25 +1,31 @@+{-# LANGUAGE DeriveDataTypeable #-}+ module Propellor.Types.Docker where -import Propellor.Types.OS+import Propellor.Types import Propellor.Types.Empty+import Propellor.Types.Info  import Data.Monoid import qualified Data.Map as M -data DockerInfo h = DockerInfo+data DockerInfo = DockerInfo 	{ _dockerRunParams :: [DockerRunParam]-	, _dockerContainers :: M.Map String h+	, _dockerContainers :: M.Map String Host 	}-	deriving (Show)+	deriving (Show, Typeable) -instance Monoid (DockerInfo h) where+instance IsInfo DockerInfo where+	propigateInfo _ = False++instance Monoid DockerInfo where 	mempty = DockerInfo mempty mempty 	mappend old new = DockerInfo 		{ _dockerRunParams = _dockerRunParams old <> _dockerRunParams new 		, _dockerContainers = M.union (_dockerContainers old) (_dockerContainers new) 		} -instance Empty (DockerInfo h) where+instance Empty DockerInfo where 	isEmpty i = and 		[ isEmpty (_dockerRunParams i) 		, isEmpty (_dockerContainers i)
+ src/Propellor/Types/Info.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Propellor.Types.Info (+	Info,+	IsInfo(..),+	addInfo,+	getInfo,+	propigatableInfo,+	InfoVal(..),+	fromInfoVal,+	Typeable,+) where++import Data.Dynamic+import Data.Monoid+import Data.Maybe++-- | Information about a Host, which can be provided by its properties.+--+-- Any value in the `IsInfo` type class can be added to an Info.+data Info = Info [(Dynamic, Bool)]++instance Show Info where+	show (Info l) = "Info " ++ show (map (dynTypeRep . fst) l)++instance Monoid Info where+ 	mempty = Info []+	mappend (Info a) (Info b) = Info (a <> b)++-- | Values stored in Info must be members of this class.+--+-- This is used to avoid accidentially using other data types+-- as info, especially type aliases which coud easily lead to bugs.+-- We want a little bit of dynamic types here, but not too far..+class (Typeable v, Monoid v) => IsInfo v where+	-- | Should info of this type be propigated out of a+	-- container to its Host?+	propigateInfo :: v -> Bool++addInfo :: IsInfo v => Info -> v -> Info+addInfo (Info l) v = Info ((toDyn v, propigateInfo v):l)++getInfo :: IsInfo v => Info -> v+getInfo (Info l) = mconcat (mapMaybe (fromDynamic . fst) (reverse l))++-- | Filters out parts of the Info that should not propigate out of a+-- container.+propigatableInfo :: Info -> Info+propigatableInfo (Info l) = Info (filter snd l)++-- | Use this to put a value in Info that is not a monoid.+-- The last value set will be used. This info does not propigate+-- out of a container.+data InfoVal v = NoInfoVal | InfoVal v+	deriving (Typeable)++instance Monoid (InfoVal v) where+	mempty = NoInfoVal+	mappend _ v@(InfoVal _) = v+	mappend v NoInfoVal = v++instance Typeable v => IsInfo (InfoVal v) where+	propigateInfo _ = False++fromInfoVal :: InfoVal v -> Maybe v+fromInfoVal NoInfoVal = Nothing+fromInfoVal (InfoVal v) = Just v
src/Propellor/Types/OS.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DeriveDataTypeable #-}+ module Propellor.Types.OS ( 	System(..), 	Distribution(..),@@ -14,10 +16,11 @@ ) where  import Network.BSD (HostName)+import Data.Typeable  -- | High level description of a operating system. data System = System Distribution Architecture-	deriving (Show, Eq)+	deriving (Show, Eq, Typeable)  data Distribution 	= Debian DebianSuite
src/Propellor/Types/PrivData.hs view
@@ -1,7 +1,12 @@ module Propellor.Types.PrivData where  import Propellor.Types.OS+import Utility.PartialPrelude+import Utility.FileSystemEncoding +import Data.Maybe+import qualified Data.ByteString.Lazy as L+ -- | Note that removing or changing constructors or changing types will -- break the serialized privdata files, so don't do that! -- It's fine to add new constructors.@@ -89,7 +94,27 @@ hostContext :: HostContext hostContext = HostContext Context -type PrivData = String+-- | Contains the actual private data.+--+-- Note that this may contain exta newlines at the end, or they may have+-- been stripped off, depending on how the user entered the privdata,+-- and which version of propellor stored it. Use the accessor functions+-- below to avoid newline problems.+newtype PrivData = PrivData String++-- | When PrivData is the content of a file, this is the lines thereof.+privDataLines :: PrivData -> [String]+privDataLines (PrivData s) = lines s++-- | When the PrivData is a single value, like a password, this extracts+-- it. Note that if multiple lines are present in the PrivData, only+-- the first is returned; there is never a newline in the String.+privDataVal :: PrivData -> String+privDataVal (PrivData s) = fromMaybe "" (headMaybe (lines s))++-- | Use to get ByteString out of PrivData.+privDataByteString :: PrivData -> L.ByteString+privDataByteString (PrivData s) = encodeBS s  data SshKeyType = SshRsa | SshDsa | SshEcdsa | SshEd25519 	deriving (Read, Show, Ord, Eq, Enum, Bounded)
− src/Propellor/Types/Val.hs
@@ -1,22 +0,0 @@-module Propellor.Types.Val where--import Data.Monoid--import Propellor.Types.Empty--data Val a = Val a | NoVal-	deriving (Eq, Show)--instance Monoid (Val a) where-	mempty = NoVal-	mappend old new = case new of-		NoVal -> old-		_ -> new--instance Empty (Val a) where-	isEmpty NoVal = True-	isEmpty _ = False--fromVal :: Val a -> Maybe a-fromVal (Val a) = Just a-fromVal NoVal = Nothing
src/Utility/Exception.hs view
@@ -1,6 +1,6 @@ {- Simple IO exception handling (and some more)  -- - Copyright 2011-2014 Joey Hess <id@joeyh.name>+ - Copyright 2011-2015 Joey Hess <id@joeyh.name>  -  - License: BSD-2-clause  -}@@ -20,6 +20,7 @@ 	catchNonAsync, 	tryNonAsync, 	tryWhenExists,+	catchHardwareFault, ) where  import Control.Monad.Catch as X hiding (Handler)@@ -27,7 +28,9 @@ import Control.Exception (IOException, AsyncException) import Control.Monad import Control.Monad.IO.Class (liftIO, MonadIO)-import System.IO.Error (isDoesNotExistError)+import System.IO.Error (isDoesNotExistError, ioeGetErrorType)+import GHC.IO.Exception (IOErrorType(..))+ import Utility.Data  {- Catches IO errors and returns a Bool -}@@ -36,10 +39,7 @@  {- Catches IO errors and returns a Maybe -} catchMaybeIO :: MonadCatch m => m a -> m (Maybe a)-catchMaybeIO a = do-	catchDefaultIO Nothing $ do-		v <- a-		return (Just v)+catchMaybeIO a = catchDefaultIO Nothing $ a >>= (return . Just)  {- Catches IO errors and returns a default value. -} catchDefaultIO :: MonadCatch m => a -> m a -> m a@@ -87,3 +87,12 @@ tryWhenExists a = do 	v <- tryJust (guard . isDoesNotExistError) a 	return (eitherToMaybe v)++{- Catches only exceptions caused by hardware faults.+ - Ie, disk IO error. -}+catchHardwareFault :: MonadCatch m => m a -> (IOException -> m a) -> m a+catchHardwareFault a onhardwareerr = catchIO a onlyhw+  where+	onlyhw e+		| ioeGetErrorType e == HardwareFault = onhardwareerr e+		| otherwise = throwM e
src/Utility/FileSystemEncoding.hs view
@@ -13,6 +13,7 @@ 	withFilePath, 	md5FilePath, 	decodeBS,+	encodeBS, 	decodeW8, 	encodeW8, 	encodeW8NUL,@@ -34,6 +35,8 @@ import qualified Data.ByteString.Lazy.UTF8 as L8 #endif +import Utility.Exception+ {- Sets a Handle to use the filesystem encoding. This causes data  - written or read from it to be encoded/decoded the same  - as ghc 7.4 does to filenames etc. This special encoding@@ -67,12 +70,16 @@  - only allows doing this conversion with CStrings, and the CString buffer  - is allocated, used, and deallocated within the call, with no side  - effects.+ -+ - If the FilePath contains a value that is not legal in the filesystem+ - encoding, rather than thowing an exception, it will be returned as-is.  -} {-# NOINLINE _encodeFilePath #-} _encodeFilePath :: FilePath -> String _encodeFilePath fp = unsafePerformIO $ do 	enc <- Encoding.getFileSystemEncoding-	GHC.withCString enc fp $ GHC.peekCString Encoding.char8+	GHC.withCString enc fp (GHC.peekCString Encoding.char8)+		`catchNonAsync` (\_ -> return fp)  {- Encodes a FilePath into a Md5.Str, applying the filesystem encoding. -} md5FilePath :: FilePath -> MD5.Str@@ -81,11 +88,19 @@ {- Decodes a ByteString into a FilePath, applying the filesystem encoding. -} decodeBS :: L.ByteString -> FilePath #ifndef mingw32_HOST_OS-decodeBS = encodeW8 . L.unpack+decodeBS = encodeW8NUL . L.unpack #else {- On Windows, we assume that the ByteString is utf-8, since Windows  - only uses unicode for filenames. -} decodeBS = L8.toString+#endif++{- Encodes a FilePath into a ByteString, applying the filesystem encoding. -}+encodeBS :: FilePath -> L.ByteString+#ifndef mingw32_HOST_OS+encodeBS = L.pack . decodeW8NUL+#else+encodeBS = L8.fromString #endif  {- Converts a [Word8] to a FilePath, encoding using the filesystem encoding.
src/Utility/Misc.hs view
@@ -136,7 +136,7 @@  - if this reap gets there first. -} reapZombies :: IO () #ifndef mingw32_HOST_OS-reapZombies = do+reapZombies = 	-- throws an exception when there are no child processes 	catchDefaultIO Nothing (getAnyProcessStatus False True) 		>>= maybe (return ()) (const reapZombies)
src/Utility/Process.hs view
@@ -31,6 +31,7 @@ 	withQuietOutput, 	feedWithQuietOutput, 	createProcess,+	waitForProcess, 	startInteractiveProcess, 	stdinHandle, 	stdoutHandle,@@ -42,7 +43,7 @@  import qualified System.Process import qualified System.Process as X hiding (CreateProcess(..), createProcess, runInteractiveProcess, readProcess, readProcessWithExitCode, system, rawSystem, runInteractiveCommand, runProcess)-import System.Process hiding (createProcess, readProcess)+import System.Process hiding (createProcess, readProcess, waitForProcess) import System.Exit import System.IO import System.Log.Logger@@ -171,7 +172,7 @@ -- returns a transcript combining its stdout and stderr, and -- whether it succeeded or failed. processTranscript :: String -> [String] -> (Maybe String) -> IO (String, Bool)-processTranscript cmd opts input = processTranscript' cmd opts Nothing input+processTranscript cmd opts = processTranscript' cmd opts Nothing  processTranscript' :: String -> [String] -> Maybe [(String, String)] -> (Maybe String) -> IO (String, Bool) processTranscript' cmd opts environ input = do@@ -345,22 +346,6 @@ processHandle :: (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> ProcessHandle processHandle (_, _, _, pid) = pid --- | Debugging trace for a CreateProcess.-debugProcess :: CreateProcess -> IO ()-debugProcess p = do-	debugM "Utility.Process" $ unwords-		[ action ++ ":"-		, showCmd p-		]-  where-	action-		| piped (std_in p) && piped (std_out p) = "chat"-		| piped (std_in p)                      = "feed"-		| piped (std_out p)                     = "read"-		| otherwise                             = "call"-	piped Inherit = False-	piped _ = True- -- | Shows the command that a CreateProcess will run. showCmd :: CreateProcess -> String showCmd = go . cmdspec@@ -385,9 +370,30 @@ 	(Just from, Just to, _, pid) <- createProcess p 	return (pid, to, from) --- | Wrapper around 'System.Process.createProcess' from System.Process,--- that does debug logging.+-- | Wrapper around 'System.Process.createProcess' that does debug logging. createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) createProcess p = do 	debugProcess p 	System.Process.createProcess p++-- | Debugging trace for a CreateProcess.+debugProcess :: CreateProcess -> IO ()+debugProcess p = debugM "Utility.Process" $ unwords+	[ action ++ ":"+	, showCmd p+	]+  where+	action+		| piped (std_in p) && piped (std_out p) = "chat"+		| piped (std_in p)                      = "feed"+		| piped (std_out p)                     = "read"+		| otherwise                             = "call"+	piped Inherit = False+	piped _ = True++-- | Wrapper around 'System.Process.waitForProcess' that does debug logging.+waitForProcess ::  ProcessHandle -> IO ExitCode+waitForProcess h = do+	r <- System.Process.waitForProcess h+	debugM "Utility.Process" ("process done " ++ show r)+	return r
src/Utility/Scheduled.hs view
@@ -286,7 +286,7 @@ fromScheduledTime (SpecificTime h m) =  	show h' ++ (if m > 0 then ":" ++ pad 2 (show m) else "") ++ " " ++ ampm   where-	pad n s = take (n - length s) (repeat '0') ++ s+	pad n s = replicate (n - length s) '0' ++ s 	(h', ampm) 		| h == 0 = (12, "AM") 		| h < 12 = (h, "AM")
src/Utility/Table.hs view
@@ -26,4 +26,4 @@ 		sumcols (map (map length) table) 	sumcols [] = repeat 0 	sumcols [r] = r-	sumcols (r1:r2:rs) = sumcols $ map (uncurry max) (zip r1 r2) : rs+	sumcols (r1:r2:rs) = sumcols $ zipWith max r1 r2 : rs
src/wrapper.hs view
@@ -74,7 +74,13 @@ 				fetchUpstreamBranch propellordir distrepo 				changeWorkingDirectory propellordir 				void $ boolSystem "git" [Param "remote", Param "rm", Param "origin"]-			, void $ boolSystem "git" [Param "clone", Param netrepo, File propellordir]+			, do+				void $ boolSystem "git" [Param "clone", Param netrepo, File propellordir]+				changeWorkingDirectory propellordir+				-- Rename origin to upstream and avoid+				-- git push to that read-only repo.+				void $ boolSystem "git" [Param "remote", Param "rename", Param "origin", Param "upstream"]+				void $ boolSystem "git" [Param "config", Param "--unset", Param "branch.master.remote", Param "upstream"] 			)  	checkRepo = whenM (doesFileExist disthead <&&> doesFileExist (propellordir </> "propellor.cabal")) $ do