packages feed

propellor 2.12.0 → 2.13.0

raw patch · 43 files changed

+1281/−305 lines, 43 filesdep +stmdep +textPVP ok

version bump matches the API change (PVP)

Dependencies added: stm, text

API changes (from Hackage documentation)

- Propellor.Engine: processChainOutput :: Handle -> IO Result
- Propellor.Message: ConsoleMessageHandle :: MessageHandle
- Propellor.Message: TextMessageHandle :: MessageHandle
- Propellor.Message: actionMessage' :: (MonadIO m, ActionResult r) => Maybe HostName -> Desc -> m r -> m r
- Propellor.Message: checkDebugMode :: IO ()
- Propellor.Message: colorLine :: MessageHandle -> ColorIntensity -> Color -> String -> IO ()
- Propellor.Message: data MessageHandle
- Propellor.Message: debug :: [String] -> IO ()
- Propellor.Message: enableDebugMode :: IO ()
- Propellor.Message: mkMessageHandle :: IO MessageHandle
- Propellor.Message: whenConsole :: MessageHandle -> IO () -> IO ()
- Propellor.Types: (<<>>) :: Combines x y => x -> y -> CombinedType x y
- Propellor.Types: instance Propellor.Types.Combines Propellor.Types.RevertableProperty (Propellor.Types.Property Propellor.Types.HasInfo)
- Propellor.Types: instance Propellor.Types.Combines Propellor.Types.RevertableProperty (Propellor.Types.Property Propellor.Types.NoInfo)
- Propellor.Types: instance Propellor.Types.Combines Propellor.Types.RevertableProperty Propellor.Types.RevertableProperty
- Propellor.Types: instance Propellor.Types.IsProp Propellor.Types.RevertableProperty
+ Propellor.Debug: checkDebugMode :: IO ()
+ Propellor.Debug: debug :: [String] -> IO ()
+ Propellor.Debug: enableDebugMode :: IO ()
+ Propellor.Message: createProcessConcurrent :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)
+ Propellor.Message: getMessageHandle :: IO MessageHandle
+ Propellor.Message: infoMessage :: MonadIO m => [String] -> m ()
+ Propellor.Message: messagesDone :: IO ()
+ Propellor.Message: processChainOutput :: Handle -> IO Result
+ Propellor.Message: withConcurrentOutput :: (MonadIO m, MonadMask m) => m a -> m a
+ Propellor.Property.Cmd: waitForProcess :: ProcessHandle -> IO ExitCode
+ Propellor.Property.Concurrent: concurrentList :: IO Int -> Desc -> PropList -> Property HasInfo
+ Propellor.Property.Concurrent: concurrentSatisfy :: Propellor Result -> Propellor Result -> Propellor Result
+ Propellor.Property.Concurrent: concurrently :: (IsProp p1, IsProp p2, Combines p1 p2, IsProp (CombinedType p1 p2)) => p1 -> p2 -> CombinedType p1 p2
+ Propellor.Property.Concurrent: getNumProcessors :: IO Int
+ Propellor.Property.Concurrent: props :: PropList
+ Propellor.Property.File: isCopyOf :: FilePath -> FilePath -> Property NoInfo
+ Propellor.Property.List: PropList :: [Property HasInfo] -> PropList
+ Propellor.Property.List: data PropList
+ Propellor.Types: instance GHC.Show.Show (Propellor.Types.RevertableProperty i)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.HasInfo) (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.HasInfo) (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.NoInfo) (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.Property Propellor.Types.NoInfo) (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.HasInfo) (Propellor.Types.Property Propellor.Types.HasInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.HasInfo) (Propellor.Types.Property Propellor.Types.NoInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.HasInfo) (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.HasInfo) (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.NoInfo) (Propellor.Types.Property Propellor.Types.HasInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.NoInfo) (Propellor.Types.Property Propellor.Types.NoInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.NoInfo) (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
+ Propellor.Types: instance Propellor.Types.Combines (Propellor.Types.RevertableProperty Propellor.Types.NoInfo) (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
+ Propellor.Types: instance Propellor.Types.IsProp (Propellor.Types.RevertableProperty Propellor.Types.HasInfo)
+ Propellor.Types: instance Propellor.Types.IsProp (Propellor.Types.RevertableProperty Propellor.Types.NoInfo)
+ Propellor.Types: instance Propellor.Types.MkRevertableProperty Propellor.Types.HasInfo Propellor.Types.HasInfo
+ Propellor.Types: instance Propellor.Types.MkRevertableProperty Propellor.Types.HasInfo Propellor.Types.NoInfo
+ Propellor.Types: instance Propellor.Types.MkRevertableProperty Propellor.Types.NoInfo Propellor.Types.HasInfo
+ Propellor.Types: instance Propellor.Types.MkRevertableProperty Propellor.Types.NoInfo Propellor.Types.NoInfo
- Propellor: (!) :: PropAccum h => h -> RevertableProperty -> h
+ Propellor: (!) :: IsProp (RevertableProperty i) => PropAccum h => h -> RevertableProperty i -> h
- Propellor: (<!>) :: Property i1 -> Property i2 -> RevertableProperty
+ Propellor: (<!>) :: MkRevertableProperty i1 i2 => Property i1 -> Property i2 -> RevertableProperty (CInfo i1 i2)
- Propellor: RevertableProperty :: (Property HasInfo) -> (Property HasInfo) -> RevertableProperty
+ Propellor: RevertableProperty :: (Property i) -> (Property i) -> RevertableProperty i
- Propellor: before :: (IsProp x, Combines y x, IsProp (CombinedType y x)) => x -> y -> CombinedType y x
+ Propellor: before :: Combines x y => x -> y -> CombinedType x y
- Propellor: data RevertableProperty
+ Propellor: data RevertableProperty i
- Propellor: onChange :: (Combines (Property x) (Property y)) => Property x -> Property y -> CombinedType (Property x) (Property y)
+ Propellor: onChange :: (Combines x y) => x -> y -> CombinedType x y
- Propellor.Message: actionMessage :: (MonadIO m, ActionResult r) => Desc -> m r -> m r
+ Propellor.Message: actionMessage :: (MonadIO m, MonadMask m, ActionResult r) => Desc -> m r -> m r
- Propellor.Message: actionMessageOn :: (MonadIO m, ActionResult r) => HostName -> Desc -> m r -> m r
+ Propellor.Message: actionMessageOn :: (MonadIO m, MonadMask m, ActionResult r) => HostName -> Desc -> m r -> m r
- Propellor.PropAccum: (!) :: PropAccum h => h -> RevertableProperty -> h
+ Propellor.PropAccum: (!) :: IsProp (RevertableProperty i) => PropAccum h => h -> RevertableProperty i -> h
- Propellor.Property: before :: (IsProp x, Combines y x, IsProp (CombinedType y x)) => x -> y -> CombinedType y x
+ Propellor.Property: before :: Combines x y => x -> y -> CombinedType x y
- Propellor.Property: fallback :: (Combines (Property p1) (Property p2)) => Property p1 -> Property p2 -> Property (CInfo p1 p2)
+ Propellor.Property: fallback :: (Combines p1 p2) => p1 -> p2 -> CombinedType p1 p2
- Propellor.Property: onChange :: (Combines (Property x) (Property y)) => Property x -> Property y -> CombinedType (Property x) (Property y)
+ Propellor.Property: onChange :: (Combines x y) => x -> y -> CombinedType x y
- Propellor.Property: onChangeFlagOnFail :: (Combines (Property x) (Property y)) => FilePath -> Property x -> Property y -> CombinedType (Property x) (Property y)
+ Propellor.Property: onChangeFlagOnFail :: (Combines x y) => FilePath -> x -> y -> CombinedType x y
- Propellor.Property: revert :: RevertableProperty -> RevertableProperty
+ Propellor.Property: revert :: RevertableProperty i -> RevertableProperty i
- Propellor.Property.Apache: modEnabled :: String -> RevertableProperty
+ Propellor.Property.Apache: modEnabled :: String -> RevertableProperty NoInfo
- Propellor.Property.Apache: siteEnabled :: HostName -> ConfigFile -> RevertableProperty
+ Propellor.Property.Apache: siteEnabled :: HostName -> ConfigFile -> RevertableProperty NoInfo
- Propellor.Property.Apache: virtualHost :: HostName -> Port -> FilePath -> RevertableProperty
+ Propellor.Property.Apache: virtualHost :: HostName -> Port -> FilePath -> RevertableProperty NoInfo
- Propellor.Property.Apt: trustsKey :: AptKey -> RevertableProperty
+ Propellor.Property.Apt: trustsKey :: AptKey -> RevertableProperty NoInfo
- Propellor.Property.Apt: unattendedUpgrades :: RevertableProperty
+ Propellor.Property.Apt: unattendedUpgrades :: RevertableProperty NoInfo
- Propellor.Property.Chroot: provisioned :: Chroot -> RevertableProperty
+ Propellor.Property.Chroot: provisioned :: Chroot -> RevertableProperty HasInfo
- Propellor.Property.Chroot: provisioned' :: (Property HasInfo -> Property HasInfo) -> Chroot -> Bool -> RevertableProperty
+ Propellor.Property.Chroot: provisioned' :: (Property HasInfo -> Property HasInfo) -> Chroot -> Bool -> RevertableProperty HasInfo
- Propellor.Property.Conductor: conducts :: Conductable c => c -> RevertableProperty
+ Propellor.Property.Conductor: conducts :: Conductable c => c -> RevertableProperty HasInfo
- Propellor.Property.Debootstrap: installed :: RevertableProperty
+ Propellor.Property.Debootstrap: installed :: RevertableProperty NoInfo
- Propellor.Property.DiskImage: imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty
+ Propellor.Property.DiskImage: imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty HasInfo
- Propellor.Property.DiskImage: imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty
+ Propellor.Property.DiskImage: imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty NoInfo
- Propellor.Property.DiskImage: imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty
+ Propellor.Property.DiskImage: imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty HasInfo
- Propellor.Property.Dns: primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty
+ Propellor.Property.Dns: primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty HasInfo
- Propellor.Property.Dns: secondary :: [Host] -> Domain -> RevertableProperty
+ Propellor.Property.Dns: secondary :: [Host] -> Domain -> RevertableProperty HasInfo
- Propellor.Property.Dns: secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty
+ Propellor.Property.Dns: secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty HasInfo
- Propellor.Property.Dns: signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty
+ Propellor.Property.Dns: signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty HasInfo
- Propellor.Property.DnsSec: keysInstalled :: Domain -> RevertableProperty
+ Propellor.Property.DnsSec: keysInstalled :: Domain -> RevertableProperty HasInfo
- Propellor.Property.DnsSec: zoneSigned :: Domain -> FilePath -> RevertableProperty
+ Propellor.Property.DnsSec: zoneSigned :: Domain -> FilePath -> RevertableProperty HasInfo
- Propellor.Property.Docker: docked :: Container -> RevertableProperty
+ Propellor.Property.Docker: docked :: Container -> RevertableProperty HasInfo
- Propellor.Property.Git: daemonRunning :: FilePath -> RevertableProperty
+ Propellor.Property.Git: daemonRunning :: FilePath -> RevertableProperty NoInfo
- Propellor.Property.Nginx: siteEnabled :: HostName -> ConfigFile -> RevertableProperty
+ Propellor.Property.Nginx: siteEnabled :: HostName -> ConfigFile -> RevertableProperty NoInfo
- Propellor.Property.Prosody: confEnabled :: Conf -> ConfigFile -> RevertableProperty
+ Propellor.Property.Prosody: confEnabled :: Conf -> ConfigFile -> RevertableProperty NoInfo
- Propellor.Property.SiteSpecific.JoeySites: apacheSite :: HostName -> Bool -> ConfigFile -> RevertableProperty
+ Propellor.Property.SiteSpecific.JoeySites: apacheSite :: HostName -> Bool -> ConfigFile -> RevertableProperty NoInfo
- Propellor.Property.SiteSpecific.JoeySites: userDirHtml :: Property HasInfo
+ Propellor.Property.SiteSpecific.JoeySites: userDirHtml :: Property NoInfo
- Propellor.Property.Ssh: listenPort :: Int -> RevertableProperty
+ Propellor.Property.Ssh: listenPort :: Int -> RevertableProperty NoInfo
- Propellor.Property.Systemd: bind :: Bindable p => p -> RevertableProperty
+ Propellor.Property.Systemd: bind :: Bindable p => p -> RevertableProperty HasInfo
- Propellor.Property.Systemd: bindRo :: Bindable p => p -> RevertableProperty
+ Propellor.Property.Systemd: bindRo :: Bindable p => p -> RevertableProperty HasInfo
- Propellor.Property.Systemd: containerCfg :: String -> RevertableProperty
+ Propellor.Property.Systemd: containerCfg :: String -> RevertableProperty HasInfo
- Propellor.Property.Systemd: linkJournal :: RevertableProperty
+ Propellor.Property.Systemd: linkJournal :: RevertableProperty HasInfo
- Propellor.Property.Systemd: masked :: ServiceName -> RevertableProperty
+ Propellor.Property.Systemd: masked :: ServiceName -> RevertableProperty NoInfo
- Propellor.Property.Systemd: nspawned :: Container -> RevertableProperty
+ Propellor.Property.Systemd: nspawned :: Container -> RevertableProperty HasInfo
- Propellor.Property.Systemd: privateNetwork :: RevertableProperty
+ Propellor.Property.Systemd: privateNetwork :: RevertableProperty HasInfo
- Propellor.Property.Systemd: publish :: Publishable p => p -> RevertableProperty
+ Propellor.Property.Systemd: publish :: Publishable p => p -> RevertableProperty HasInfo
- Propellor.Property.Systemd: resolvConfed :: RevertableProperty
+ Propellor.Property.Systemd: resolvConfed :: RevertableProperty HasInfo
- Propellor.Property.Uwsgi: appEnabled :: AppName -> ConfigFile -> RevertableProperty
+ Propellor.Property.Uwsgi: appEnabled :: AppName -> ConfigFile -> RevertableProperty NoInfo
- Propellor.Types: (<!>) :: Property i1 -> Property i2 -> RevertableProperty
+ Propellor.Types: (<!>) :: MkRevertableProperty i1 i2 => Property i1 -> Property i2 -> RevertableProperty (CInfo i1 i2)
- Propellor.Types: RevertableProperty :: (Property HasInfo) -> (Property HasInfo) -> RevertableProperty
+ Propellor.Types: RevertableProperty :: (Property i) -> (Property i) -> RevertableProperty i
- Propellor.Types: combineWith :: (Combines (Property x) (Property y)) => (Propellor Result -> Propellor Result -> Propellor Result) -> Property x -> Property y -> CombinedType (Property x) (Property y)
+ Propellor.Types: combineWith :: Combines x y => ResultCombiner -> ResultCombiner -> x -> y -> CombinedType x y
- Propellor.Types: data RevertableProperty
+ Propellor.Types: data RevertableProperty i
- Propellor.Utilities: processTranscript' :: String -> [String] -> Maybe [(String, String)] -> (Maybe String) -> IO (String, Bool)
+ Propellor.Utilities: processTranscript' :: (CreateProcess -> CreateProcess) -> String -> [String] -> Maybe String -> IO (String, Bool)

Files

CHANGELOG view
@@ -1,3 +1,33 @@+propellor (2.13.0) unstable; urgency=medium++  * RevertableProperty used to be assumed to contain info, but this is+    now made explicit, with RevertableProperty HasInfo or+    RevertableProperty NoInfo. (API change)+    Transition guide:+      - If you define a RevertableProperty, expect some type check+        failures like: "Expecting one more argument to ‘RevertableProperty’".+      - Change it to "RevertableProperty NoInfo"+      - The compiler will then tell you if it needs "HasInfo" instead.+      - If you have code that uses the RevertableProperty constructor+        that fails to type check, use the more powerful <!> operator+        instead to create the RevertableProperty.+  * Various property combinators that combined a RevertableProperty+    with a non-revertable property used to yield a RevertableProperty.+    This was a bug, because the combined property could not be fully+    reverted in many cases, and the result is now a non-revertable property.+  * combineWith now takes an additional parameter to control how revert+    actions are combined (API change).+  * Added Propellor.Property.Concurrent for concurrent properties.+  * Made the execProcess exported by propellor, and everything built on it,+    avoid scrambled output when run concurrently.+  * Propellor now depends on STM and text.+  * The cabal file now builds propellor with -O. While -O0 makes ghc+    take less memory while building propellor, it can lead to bad memory+    usage at runtime due to eg, disabled stream fusion.+  * Add File.isCopyOf. Thanks, Per Olofsson.++ -- Joey Hess <id@joeyh.name>  Sun, 08 Nov 2015 14:51:15 -0400+ propellor (2.12.0) unstable; urgency=medium    * The DiskImage module can now make bootable images using grub.
config-joey.hs view
@@ -515,13 +515,13 @@ 	& Dns.secondary hosts "ikiwiki.info" 	& Dns.secondary hosts "olduse.net" -branchableSecondary :: RevertableProperty+branchableSecondary :: RevertableProperty HasInfo branchableSecondary = Dns.secondaryFor ["branchable.com"] hosts "branchable.com"  -- Currently using kite (ns4) as primary with secondaries -- elephant (ns3) and gandi. -- kite handles all mail.-myDnsPrimary :: Bool -> Domain -> [(BindDomain, Record)] -> RevertableProperty+myDnsPrimary :: Bool -> Domain -> [(BindDomain, Record)] -> RevertableProperty HasInfo myDnsPrimary dnssec domain extras = (if dnssec then Dns.signedPrimary (Weekly Nothing) else Dns.primary) hosts domain 	(Dns.mkSOA "ns4.kitenet.net" 100) $ 	[ (RootDomain, NS $ AbsDomain "ns4.kitenet.net")
debian/changelog view
@@ -1,3 +1,33 @@+propellor (2.13.0) unstable; urgency=medium++  * RevertableProperty used to be assumed to contain info, but this is+    now made explicit, with RevertableProperty HasInfo or+    RevertableProperty NoInfo. (API change)+    Transition guide:+      - If you define a RevertableProperty, expect some type check+        failures like: "Expecting one more argument to ‘RevertableProperty’".+      - Change it to "RevertableProperty NoInfo"+      - The compiler will then tell you if it needs "HasInfo" instead.+      - If you have code that uses the RevertableProperty constructor+        that fails to type check, use the more powerful <!> operator+        instead to create the RevertableProperty.+  * Various property combinators that combined a RevertableProperty+    with a non-revertable property used to yield a RevertableProperty.+    This was a bug, because the combined property could not be fully+    reverted in many cases, and the result is now a non-revertable property.+  * combineWith now takes an additional parameter to control how revert+    actions are combined (API change).+  * Added Propellor.Property.Concurrent for concurrent properties.+  * Made the execProcess exported by propellor, and everything built on it,+    avoid scrambled output when run concurrently.+  * Propellor now depends on STM and text.+  * The cabal file now builds propellor with -O. While -O0 makes ghc+    take less memory while building propellor, it can lead to bad memory+    usage at runtime due to eg, disabled stream fusion.+  * Add File.isCopyOf. Thanks, Per Olofsson.++ -- Joey Hess <id@joeyh.name>  Sun, 08 Nov 2015 14:51:15 -0400+ propellor (2.12.0) unstable; urgency=medium    * The DiskImage module can now make bootable images using grub.
debian/control view
@@ -17,7 +17,9 @@ 	libghc-mtl-dev, 	libghc-transformers-dev, 	libghc-exceptions-dev (>= 0.6),-Maintainer: Gergely Nagy <algernon@madhouse-project.org>+	libghc-stm-dev,+	libghc-text-dev,+Maintainer: Joey Hess <id@joeyh.name> Standards-Version: 3.9.6 Vcs-Git: git://git.joeyh.name/propellor Homepage: http://propellor.branchable.com/@@ -39,6 +41,8 @@ 	libghc-mtl-dev, 	libghc-transformers-dev, 	libghc-exceptions-dev (>= 0.6),+	libghc-stm-dev,+	libghc-text-dev, 	git, 	make, Description: property-based host configuration management in haskell
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 2.12.0+Version: 2.13.0 Cabal-Version: >= 1.8 License: BSD3 Maintainer: Joey Hess <id@joeyh.name>@@ -34,35 +34,35 @@  Executable propellor   Main-Is: wrapper.hs-  GHC-Options: -threaded -O0 -Wall -fno-warn-tabs+  GHC-Options: -threaded -Wall -fno-warn-tabs   Hs-Source-Dirs:  src   Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5,    IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,    containers (>= 0.5), network, async, time, QuickCheck, mtl, transformers,-   exceptions (>= 0.6)+   exceptions (>= 0.6), stm, text    if (! os(windows))     Build-Depends: unix  Executable propellor-config   Main-Is: config.hs-  GHC-Options: -threaded -O0 -Wall -fno-warn-tabs+  GHC-Options: -threaded -Wall -fno-warn-tabs   Hs-Source-Dirs:  src   Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5,    IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,    containers (>= 0.5), network, async, time, QuickCheck, mtl, transformers,-   exceptions+   exceptions, stm, text    if (! os(windows))     Build-Depends: unix  Library-  GHC-Options: -O0 -Wall -fno-warn-tabs+  GHC-Options: -Wall -fno-warn-tabs   Hs-Source-Dirs:  src   Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5,    IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,    containers (>= 0.5), network, async, time, QuickCheck, mtl, transformers,-   exceptions+   exceptions, stm, text    if (! os(windows))     Build-Depends: unix@@ -76,6 +76,7 @@     Propellor.Property.Apache     Propellor.Property.Apt     Propellor.Property.Cmd+    Propellor.Property.Concurrent     Propellor.Property.Conductor     Propellor.Property.Hostname     Propellor.Property.Chroot@@ -134,6 +135,7 @@     Propellor.CmdLine     Propellor.Info     Propellor.Message+    Propellor.Debug     Propellor.PrivData     Propellor.Engine     Propellor.Exception@@ -174,6 +176,7 @@     Utility.PartialPrelude     Utility.PosixFiles     Utility.Process+    Utility.Process.Shim     Utility.SafeCommand     Utility.Scheduled     Utility.Table@@ -181,6 +184,9 @@     Utility.Tmp     Utility.UserInfo     Utility.QuickCheck+    System.Console.Concurrent+    System.Console.Concurrent.Internal+    System.Process.Concurrent  source-repository head   type: git
src/Propellor/Base.hs view
@@ -15,6 +15,7 @@ 	, module Propellor.Engine 	, module Propellor.Exception 	, module Propellor.Message+	, module Propellor.Debug 	, module Propellor.Location 	, module Propellor.Utilities @@ -39,6 +40,7 @@ import Propellor.PrivData import Propellor.Types.PrivData import Propellor.Message+import Propellor.Debug import Propellor.Exception import Propellor.Info import Propellor.PropAccum
src/Propellor/Bootstrap.hs view
@@ -65,7 +65,7 @@  	aptinstall p = "apt-get --no-upgrade --no-install-recommends -y install " ++ p -	-- This is the same build deps listed in debian/control.+	-- This is the same deps listed in debian/control. 	debdeps = 		[ "gnupg" 		, "ghc"@@ -81,6 +81,8 @@ 		, "libghc-mtl-dev" 		, "libghc-transformers-dev" 		, "libghc-exceptions-dev"+		, "libghc-stm-dev"+		, "libghc-text-dev" 		, "make" 		] 
src/Propellor/CmdLine.hs view
@@ -89,7 +89,7 @@  -- | Runs propellor on hosts, as controlled by command-line options. defaultMain :: [Host] -> IO ()-defaultMain hostlist = do+defaultMain hostlist = withConcurrentOutput $ do 	Shim.cleanEnv 	checkDebugMode 	cmdline <- processCmdLine@@ -120,8 +120,9 @@ 	go False (Spin hs mrelay) = do 		commitSpin 		forM_ hs $ \hn -> withhost hn $ spin mrelay hn-	go False cmdline@(SimpleRun hn) = buildFirst cmdline $-		go False (Run hn)+	go False cmdline@(SimpleRun hn) = do+		forceConsole+		buildFirst cmdline $ go False (Run hn) 	go False (Run hn) = ifM ((==) 0 <$> getRealUserID) 		( onlyprocess $ withhost hn mainProperties 		, go True (Spin [hn] Nothing)
+ src/Propellor/Debug.hs view
@@ -0,0 +1,36 @@+module Propellor.Debug where++import Control.Applicative+import Control.Monad.IfElse+import System.IO+import System.Directory+import System.Log.Logger+import System.Log.Formatter+import System.Log.Handler (setFormatter)+import System.Log.Handler.Simple++import Utility.Monad+import Utility.Env+import Utility.Exception+import Utility.Process++debug :: [String] -> IO ()+debug = debugM "propellor" . unwords++checkDebugMode :: IO ()+checkDebugMode = go =<< getEnv "PROPELLOR_DEBUG"+  where+	go (Just "1") = enableDebugMode+	go (Just _) = noop+	go Nothing = whenM (doesDirectoryExist ".git") $+		whenM (elem "1" . lines <$> getgitconfig) enableDebugMode+	getgitconfig = catchDefaultIO "" $+		readProcess "git" ["config", "propellor.debug"]++enableDebugMode :: IO ()+enableDebugMode = do+	f <- setFormatter+		<$> streamHandler stderr DEBUG+		<*> pure (simpleLogFormatter "[$time] $msg")+	updateGlobalLogger rootLoggerName $ +		setLevel DEBUG .  setHandlers [f]
src/Propellor/Engine.hs view
@@ -9,14 +9,12 @@ 	fromHost, 	fromHost', 	onlyProcess,-	processChainOutput, ) where  import System.Exit import System.IO import Data.Monoid import Control.Applicative-import System.Console.ANSI import "mtl" Control.Monad.RWS.Strict import System.PosixCompat import System.Posix.IO@@ -29,8 +27,6 @@ import Propellor.Info import Propellor.Property import Utility.Exception-import Utility.PartialPrelude-import Utility.Monad  -- | Gets the Properties of a Host, and ensures them all, -- with nice display of what's being done.@@ -38,10 +34,7 @@ mainProperties host = do 	ret <- runPropellor host $ 		ensureProperties [ignoreInfo $ infoProperty "overall" (ensureProperties ps) mempty mempty]-	h <- mkMessageHandle-        whenConsole h $-		setTitle "propellor: done"-	hFlush stdout+	messagesDone 	case ret of 		FailedChange -> exitWith (ExitFailure 1) 		_ -> exitWith ExitSuccess@@ -55,7 +48,7 @@ -- are then also run. runPropellor :: Host -> Propellor Result -> IO Result runPropellor host a = do-	(res, _s, endactions) <- runRWST (runWithHost a) host ()+	(res, endactions) <- evalRWST (runWithHost a) host () 	endres <- mapM (runEndAction host res) endactions 	return $ mconcat (res:endres) @@ -99,28 +92,3 @@ 		return l 	unlock = closeFd 	alreadyrunning = error "Propellor is already running on this host!"---- | Reads and displays each line from the Handle, except for the last line--- which is a Result.-processChainOutput :: Handle -> IO Result-processChainOutput h = go Nothing-  where-	go lastline = do-		v <- catchMaybeIO (hGetLine h)-		debug ["read from chained propellor: ", show v]-		case v of-			Nothing -> case lastline of-				Nothing -> do-					debug ["chained propellor output nothing; assuming it failed"]-					return FailedChange-				Just l -> case readish l of-					Just r -> pure r-					Nothing -> do-						debug ["chained propellor output did not end with a Result; assuming it failed"]-						putStrLn l-						hFlush stdout-						return FailedChange-			Just s -> do-				maybe noop (\l -> unless (null l) (putStrLn l)) lastline-				hFlush stdout-				go (Just s)
src/Propellor/Gpg.hs view
@@ -7,6 +7,8 @@ import Data.Maybe import Data.List.Utils import Control.Monad+import System.Console.Concurrent+import System.Console.Concurrent.Internal (ConcurrentProcessHandle(..))  import Propellor.PrivData.Paths import Propellor.Message@@ -111,10 +113,7 @@ 	-- Commit explicitly the keyring and privdata files, as other 	-- changes may be staged by the user and shouldn't be committed. 	tocommit <- filterM doesFileExist [ privDataFile, keyring]-	gitCommit $ (map File tocommit) ++ -		[ Param "-m"-		, Param ("propellor " ++ action)-		]+	gitCommit (Just ("propellor " ++ action)) (map File tocommit)  -- Adds --gpg-sign if there's a keyring. gpgSignParams :: [CommandParam] -> IO [CommandParam]@@ -124,10 +123,17 @@ 	)  -- Automatically sign the commit if there'a a keyring.-gitCommit :: [CommandParam] -> IO Bool-gitCommit ps = do-	ps' <- gpgSignParams ps-	boolSystem "git" (Param "commit" : ps')+gitCommit :: Maybe String -> [CommandParam] -> IO Bool+gitCommit msg ps = do+	let ps' = Param "commit" : ps ++ +		maybe [] (\m -> [Param "-m", Param m]) msg+	ps'' <- gpgSignParams ps'+	if isNothing msg+		then do+			(_, _, _, ConcurrentProcessHandle p) <- createProcessForeground $+				proc "git" (toCommand ps'')+			checkSuccessProcess p+		else boolSystem "git" ps''  gpgDecrypt :: FilePath -> IO String gpgDecrypt f = ifM (doesFileExist f)
src/Propellor/Message.hs view
@@ -1,125 +1,148 @@-{-# LANGUAGE PackageImports #-}+-- | This module handles all display of output to the console when+-- propellor is ensuring Properties.+--+-- When two threads both try to display a message concurrently, +-- the messages will be displayed sequentially. -module Propellor.Message where+module Propellor.Message (+	getMessageHandle,+	isConsole,+	forceConsole,+	actionMessage,+	actionMessageOn,+	warningMessage,+	infoMessage,+	errorMessage,+	processChainOutput,+	messagesDone,+	createProcessConcurrent,+	withConcurrentOutput,+) where  import System.Console.ANSI import System.IO-import System.Log.Logger-import System.Log.Formatter-import System.Log.Handler (setFormatter)-import System.Log.Handler.Simple-import "mtl" Control.Monad.Reader-import Data.Maybe+import Control.Monad.IO.Class (liftIO, MonadIO) import Control.Applicative-import System.Directory-import Control.Monad.IfElse+import System.IO.Unsafe (unsafePerformIO)+import Control.Concurrent+import System.Console.Concurrent  import Propellor.Types+import Utility.PartialPrelude import Utility.Monad-import Utility.Env-import Utility.Process import Utility.Exception -data MessageHandle-	= ConsoleMessageHandle-	| TextMessageHandle+data MessageHandle = MessageHandle+	{ isConsole :: Bool+	} -mkMessageHandle :: IO MessageHandle-mkMessageHandle = do-	ifM (hIsTerminalDevice stdout <||> (isJust <$> getEnv "PROPELLOR_CONSOLE"))-		( return ConsoleMessageHandle-		, return TextMessageHandle-		)+-- | A shared global variable for the MessageHandle.+{-# NOINLINE globalMessageHandle #-}+globalMessageHandle :: MVar MessageHandle+globalMessageHandle = unsafePerformIO $ +	newMVar =<< MessageHandle+		<$> hIsTerminalDevice stdout -forceConsole :: IO ()-forceConsole = void $ setEnv "PROPELLOR_CONSOLE" "1" True+-- | Gets the global MessageHandle.+getMessageHandle :: IO MessageHandle+getMessageHandle = readMVar globalMessageHandle -isConsole :: MessageHandle -> Bool-isConsole ConsoleMessageHandle = True-isConsole _ = False+-- | Force console output. This can be used when stdout is not directly+-- connected to a console, but is eventually going to be displayed at a+-- console.+forceConsole :: IO ()+forceConsole = modifyMVar_ globalMessageHandle $ \mh ->+	pure (mh { isConsole = True }) -whenConsole :: MessageHandle -> IO () -> IO ()-whenConsole ConsoleMessageHandle a = a-whenConsole _ _ = return ()+whenConsole :: String -> IO String+whenConsole s = ifM (isConsole <$> getMessageHandle)+	( pure s+	, pure ""+	)  -- | Shows a message while performing an action, with a colored status -- display.-actionMessage :: (MonadIO m, ActionResult r) => Desc -> m r -> m r+actionMessage :: (MonadIO m, MonadMask m, ActionResult r) => Desc -> m r -> m r actionMessage = actionMessage' Nothing  -- | Shows a message while performing an action on a specified host, -- with a colored status display.-actionMessageOn :: (MonadIO m, ActionResult r) => HostName -> Desc -> m r -> m r+actionMessageOn :: (MonadIO m, MonadMask m, ActionResult r) => HostName -> Desc -> m r -> m r actionMessageOn = actionMessage' . Just -actionMessage' :: (MonadIO m, ActionResult r) => Maybe HostName -> Desc -> m r -> m r+actionMessage' :: (MonadIO m, MonadMask m, ActionResult r) => Maybe HostName -> Desc -> m r -> m r actionMessage' mhn desc a = do-	h <- liftIO mkMessageHandle-	liftIO $ whenConsole h $ do-		setTitle $ "propellor: " ++ desc-		hFlush stdout+	liftIO $ outputConcurrent+		=<< whenConsole (setTitleCode $ "propellor: " ++ desc)  	r <- a -	liftIO $ do-		whenConsole h $-			setTitle "propellor: running"-		showhn h mhn-		putStr $ desc ++ " ... "-		let (msg, intensity, color) = getActionResult r-		colorLine h intensity color msg-		hFlush stdout+	liftIO $ outputConcurrent . concat =<< sequence+		[ whenConsole $+			setTitleCode "propellor: running"+		, showhn mhn+		, pure $ desc ++ " ... "+		, let (msg, intensity, color) = getActionResult r+		  in colorLine intensity color msg+		]  	return r   where-	showhn _ Nothing = return ()-	showhn h (Just hn) = do-		whenConsole h $-			setSGR [SetColor Foreground Dull Cyan]-		putStr (hn ++ " ")-		whenConsole h $-			setSGR []+	showhn Nothing = return ""+	showhn (Just hn) = concat <$> sequence+		[ whenConsole $+			setSGRCode [SetColor Foreground Dull Cyan]+		, pure (hn ++ " ")+		, whenConsole $+			setSGRCode []+		]  warningMessage :: MonadIO m => String -> m ()-warningMessage s = liftIO $ do-	h <- mkMessageHandle-	colorLine h Vivid Magenta $ "** warning: " ++ s+warningMessage s = liftIO $+	outputConcurrent =<< colorLine Vivid Magenta ("** warning: " ++ s) +infoMessage :: MonadIO m => [String] -> m ()+infoMessage ls = liftIO $ outputConcurrent $ concatMap (++ "\n") ls+ errorMessage :: MonadIO m => String -> m a errorMessage s = liftIO $ do-	h <- mkMessageHandle-	colorLine h Vivid Red $ "** error: " ++ s+	outputConcurrent =<< colorLine Vivid Red ("** error: " ++ s) 	error "Cannot continue!"--colorLine :: MessageHandle -> ColorIntensity -> Color -> String -> IO ()-colorLine h intensity color msg = do-	whenConsole h $-		setSGR [SetColor Foreground intensity color]-	putStr msg-	whenConsole h $-		setSGR []+ +colorLine :: ColorIntensity -> Color -> String -> IO String+colorLine intensity color msg = concat <$> sequence+	[ whenConsole $+		setSGRCode [SetColor Foreground intensity color]+	, pure msg+	, whenConsole $+		setSGRCode [] 	-- Note this comes after the color is reset, so that 	-- the color set and reset happen in the same line.-	putStrLn ""-	hFlush stdout--debug :: [String] -> IO ()-debug = debugM "propellor" . unwords+	, pure "\n"+	] -checkDebugMode :: IO ()-checkDebugMode = go =<< getEnv "PROPELLOR_DEBUG"+-- | Reads and displays each line from the Handle, except for the last line+-- which is a Result.+processChainOutput :: Handle -> IO Result+processChainOutput h = go Nothing   where-	go (Just "1") = enableDebugMode-	go (Just _) = noop-	go Nothing = whenM (doesDirectoryExist ".git") $-		whenM (elem "1" . lines <$> getgitconfig) enableDebugMode-	getgitconfig = catchDefaultIO "" $-		readProcess "git" ["config", "propellor.debug"]+	go lastline = do+		v <- catchMaybeIO (hGetLine h)+		case v of+			Nothing -> case lastline of+				Nothing -> do+					return FailedChange+				Just l -> case readish l of+					Just r -> pure r+					Nothing -> do+						outputConcurrent (l ++ "\n")+						return FailedChange+			Just s -> do+				outputConcurrent $+					maybe "" (\l -> if null l then "" else l ++ "\n") lastline+				go (Just s) -enableDebugMode :: IO ()-enableDebugMode = do-	f <- setFormatter-		<$> streamHandler stderr DEBUG-		<*> pure (simpleLogFormatter "[$time] $msg")-	updateGlobalLogger rootLoggerName $ -		setLevel DEBUG .  setHandlers [f]+-- | Called when all messages about properties have been printed.+messagesDone :: IO ()+messagesDone = outputConcurrent+	=<< whenConsole (setTitleCode "propellor: done")
src/Propellor/PrivData.hs view
@@ -36,6 +36,8 @@ import qualified Data.Map as M import qualified Data.Set as S import qualified Data.ByteString.Lazy as L+import System.Console.Concurrent+import System.Console.Concurrent.Internal (ConcurrentProcessHandle(..))  import Propellor.Types import Propellor.Types.PrivData@@ -54,6 +56,7 @@ import Utility.Env import Utility.Table import Utility.FileSystemEncoding+import Utility.Process  -- | Allows a Property to access the value of a specific PrivDataField, -- for use in a specific Context or HostContext.@@ -106,9 +109,9 @@ 	missing = do 		Context cname <- mkHostContext hc <$> asks hostName 		warningMessage $ "Missing privdata " ++ intercalate " or " fieldnames ++ " (for " ++ cname ++ ")"-		liftIO $ putStrLn $ "Fix this by running:"-		liftIO $ showSet $-			map (\s -> (privDataField s, Context cname, describePrivDataSource s)) srclist+		infoMessage $ +			"Fix this by running:" :+			showSet (map (\s -> (privDataField s, Context cname, describePrivDataSource s)) srclist) 		return FailedChange 	addinfo p = infoProperty 		(propertyDesc p)@@ -121,11 +124,14 @@ 	fieldlist = map privDataField srclist 	hc = asHostContext c -showSet :: [(PrivDataField, Context, Maybe PrivDataSourceDesc)] -> IO ()-showSet l = forM_ l $ \(f, Context c, md) -> do-	putStrLn $ "  propellor --set '" ++ show f ++ "' '" ++ c ++ "' \\"-	maybe noop (\d -> putStrLn $ "    " ++ d) md-	putStrLn ""+showSet :: [(PrivDataField, Context, Maybe PrivDataSourceDesc)] -> [String]+showSet = concatMap go+  where+	go (f, Context c, md) = catMaybes+		[ Just $ "  propellor --set '" ++ show f ++ "' '" ++ c ++ "' \\"+		, maybe Nothing (\d -> Just $ "    " ++ d) md+		, Just ""+		]  addPrivData :: (PrivDataField, Maybe PrivDataSourceDesc, HostContext) -> Property HasInfo addPrivData v = pureInfoProperty (show v) (PrivInfo (S.singleton v))@@ -189,7 +195,8 @@ 		hClose th 		maybe noop (\p -> writeFileProtected' f (`L.hPut` privDataByteString p)) v 		editor <- getEnvDefault "EDITOR" "vi"-		unlessM (boolSystem editor [File f]) $+		(_, _, _, ConcurrentProcessHandle p) <- createProcessForeground $ proc editor [f]+		unlessM (checkSuccessProcess p) $ 			error "Editor failed; aborting." 		PrivData <$> readFile f 	setPrivDataTo field context v'@@ -207,7 +214,8 @@ 		showtable $ map mkrow missing  		section "How to set missing data:"-		showSet $ map (\(f, c) -> (f, c, join $ M.lookup (f, c) descmap)) missing+		mapM_ putStrLn $ showSet $+			map (\(f, c) -> (f, c, join $ M.lookup (f, c) descmap)) missing   where 	header = ["Field", "Context", "Used by"] 	mkrow k@(field, Context context) =
src/Propellor/PropAccum.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE PackageImports #-}+{-# LANGUAGE PackageImports, FlexibleContexts #-}  module Propellor.PropAccum 	( host@@ -46,7 +46,7 @@ (&^) = addPropFront  -- | Adds a property in reverted form.-(!) :: PropAccum h => h -> RevertableProperty -> h+(!) :: IsProp (RevertableProperty i) => PropAccum h => h -> RevertableProperty i -> h h ! p = h & revert p  infixl 1 &
src/Propellor/Property.hs view
@@ -66,30 +66,43 @@  -- | Indicates that the first property depends on the second, -- so before the first is ensured, the second must be ensured.+--+-- The combined property uses the description of the first property. requires :: Combines x y => x -> y -> CombinedType x y-requires = (<<>>)+requires = combineWith+	-- Run action of y, then x+	(flip (<>))+	-- When reverting, run in reverse order.+	(<>)  -- | Combines together two properties, resulting in one property -- that ensures the first, and if the first succeeds, ensures the second. -- -- The combined 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 :: Combines x y => x -> y -> CombinedType x y+before = combineWith+	-- Run action of x, then y+	(<>)+	-- When reverting, run in reverse order.+	(flip (<>))  -- | Whenever a change has to be made for a Property, causes a hook -- Property to also be run, but not otherwise. onChange-	:: (Combines (Property x) (Property y))-	=> Property x-        -> Property y-        -> CombinedType (Property x) (Property y)-onChange = combineWith $ \p hook -> do-	r <- p-	case r of-		MadeChange -> do-			r' <- hook-			return $ r <> r'-		_ -> return r+	:: (Combines x y)+	=> x+        -> y+        -> CombinedType x y+onChange = combineWith combiner revertcombiner+  where+	combiner p hook = do+		r <- p+		case r of+			MadeChange -> do+				r' <- hook+				return $ r <> r'+			_ -> return r+	revertcombiner = (<>)  -- | Same as `onChange` except that if property y fails, a flag file -- is generated. On next run, if the flag file is present, property y@@ -99,14 +112,14 @@ -- `FailedChange`. But if this property is applied again, it returns -- `NoChange`. This behavior can cause trouble... onChangeFlagOnFail-	:: (Combines (Property x) (Property y))+	:: (Combines x y) 	=> FilePath-        -> Property x-        -> Property y-        -> CombinedType (Property x) (Property y)-onChangeFlagOnFail flagfile = combineWith go+        -> x+        -> y+        -> CombinedType x y+onChangeFlagOnFail flagfile = combineWith combiner revertcombiner   where-	go s1 s2 = do+	combiner s1 s2 = do 		r1 <- s1 		case r1 of 			MadeChange -> flagFailed s2@@ -114,6 +127,7 @@ 				(flagFailed s2 				, return r1 				)+	revertcombiner = (<>) 	flagFailed s = do 		r <- s 		liftIO $ case r of@@ -151,12 +165,15 @@  -- | Tries the first property, but if it fails to work, instead uses -- the second.-fallback :: (Combines (Property p1) (Property p2)) => Property p1 -> Property p2 -> Property (CInfo p1 p2)-fallback = combineWith $ \a1 a2 -> do-	r <- a1-	if r == FailedChange-		then a2-		else return r+fallback :: (Combines p1 p2) => p1 -> p2 -> CombinedType p1 p2+fallback = combineWith combiner revertcombiner+  where+	combiner a1 a2 = do+		r <- a1+		if r == FailedChange+			then a2+			else return r+	revertcombiner = (<>)  -- | Marks a Property as trivial. It can only return FailedChange or -- NoChange. @@ -184,7 +201,7 @@ withOS desc a = property desc $ a =<< getOS  -- | Undoes the effect of a RevertableProperty.-revert :: RevertableProperty -> RevertableProperty+revert :: RevertableProperty i -> RevertableProperty i revert (RevertableProperty p1 p2) = RevertableProperty p2 p1  makeChange :: IO () -> Propellor Result
src/Propellor/Property/Apache.hs view
@@ -16,7 +16,7 @@  -- | A basic virtual host, publishing a directory, and logging to -- the combined apache log file.-virtualHost :: HostName -> Port -> FilePath -> RevertableProperty+virtualHost :: HostName -> Port -> FilePath -> RevertableProperty NoInfo virtualHost hn (Port p) docroot = siteEnabled hn 	[ "<VirtualHost *:"++show p++">" 	, "ServerName "++hn++":"++show p@@ -30,7 +30,7 @@  type ConfigFile = [String] -siteEnabled :: HostName -> ConfigFile -> RevertableProperty+siteEnabled :: HostName -> ConfigFile -> RevertableProperty NoInfo siteEnabled hn cf = enable <!> disable   where 	enable = combineProperties ("apache site enabled " ++ hn)@@ -59,7 +59,7 @@   where 	comment = "# deployed with propellor, do not modify" -modEnabled :: String -> RevertableProperty+modEnabled :: String -> RevertableProperty NoInfo modEnabled modname = enable <!> disable   where 	enable = check (not <$> isenabled) $
src/Propellor/Property/Apt.hs view
@@ -212,7 +212,7 @@ 	`describe` "apt autoremove"  -- | Enables unattended upgrades. Revert to disable.-unattendedUpgrades :: RevertableProperty+unattendedUpgrades :: RevertableProperty NoInfo unattendedUpgrades = enable <!> disable   where 	enable = setup True@@ -272,7 +272,7 @@ 	, pubkey :: String 	} -trustsKey :: AptKey -> RevertableProperty+trustsKey :: AptKey -> RevertableProperty NoInfo trustsKey k = trustsKey' k <!> untrustKey k  trustsKey' :: AptKey -> Property NoInfo
src/Propellor/Property/Chroot.hs view
@@ -31,6 +31,7 @@ import qualified Data.Map as M import Data.List.Utils import System.Posix.Directory+import System.Console.Concurrent  -- | Specification of a chroot. Normally you'll use `debootstrapped` or -- `bootstrapped` to construct a Chroot value.@@ -116,10 +117,10 @@ -- Reverting this property removes the chroot. Anything mounted inside it -- is first unmounted. Note that it does not ensure that any processes -- that might be running inside the chroot are stopped.-provisioned :: Chroot -> RevertableProperty+provisioned :: Chroot -> RevertableProperty HasInfo provisioned c = provisioned' (propagateChrootInfo c) c False -provisioned' :: (Property HasInfo -> Property HasInfo) -> Chroot -> Bool -> RevertableProperty+provisioned' :: (Property HasInfo -> Property HasInfo) -> Chroot -> Bool -> RevertableProperty HasInfo provisioned' propigator c@(Chroot loc bootstrapper _) systemdonly = 	(propigator $ propertyList (chrootDesc c "exists") [setup]) 		<!>@@ -193,7 +194,7 @@  toChain :: HostName -> Chroot -> Bool -> IO CmdLine toChain parenthost (Chroot loc _ _) systemdonly = do-	onconsole <- isConsole <$> mkMessageHandle+	onconsole <- isConsole <$> getMessageHandle 	return $ ChrootChain parenthost loc systemdonly onconsole  chain :: [Host] -> CmdLine -> IO ()@@ -213,6 +214,7 @@ 					then [Systemd.installed] 					else map ignoreInfo $ 						hostProperties h+			flushConcurrentOutput 			putStrLn $ "\n" ++ show r chain _ _ = errorMessage "bad chain command" 
src/Propellor/Property/Cmd.hs view
@@ -16,6 +16,7 @@ 	safeSystemEnv, 	shellEscape, 	createProcess,+	waitForProcess, ) where  import Control.Applicative@@ -26,7 +27,7 @@ import Propellor.Property import Utility.SafeCommand import Utility.Env-import Utility.Process (createProcess, CreateProcess)+import Utility.Process (createProcess, CreateProcess, waitForProcess)  -- | A property that can be satisfied by running a command. --
+ src/Propellor/Property/Concurrent.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE FlexibleContexts #-}++-- | Propellor properties can be made to run concurrently, using this+-- module. This can speed up propellor, at the expense of using more CPUs+-- and other resources.+--+-- It's up to you to make sure that properties that you make run concurrently+-- don't implicitly depend on one-another. The worst that can happen+-- though, is that propellor fails to ensure some of the properties,+-- and tells you what went wrong.+--+-- Another potential problem is that output of concurrent properties could+-- interleave into a scrambled mess. This is mostly prevented; all messages+-- output by propellor are concurrency safe, including `errorMessage`,+-- `infoMessage`, etc. However, if you write a property that directly+-- uses `print` or `putStrLn`, you can still experience this problem.+--+-- Similarly, when properties run external commands, the command's output+-- can be a problem for concurrency. No need to worry;+-- `Propellor.Property.Cmd.createProcess` is concurrent output safe+-- (it actually uses `Propellor.Message.createProcessConcurrent`), and+-- everything else in propellor that runs external commands is built on top+-- of that. Of course, if you import System.Process and use it in a+-- property, you can bypass that and shoot yourself in the foot.+--+-- Finally, anything that directly accesses the tty can bypass+-- these protections. That's sometimes done for eg, password prompts.+-- A well-written property should avoid running interactive commands+-- anyway.++module Propellor.Property.Concurrent (+	concurrently,+	concurrentList,+	props,+	getNumProcessors,+	concurrentSatisfy,+) where++import Propellor.Base++import Control.Concurrent+import qualified Control.Concurrent.Async as A+import GHC.Conc (getNumProcessors)+import Control.Monad.RWS.Strict++-- | Ensures two properties concurrently.+--+-- >	& foo `concurrently` bar+--+-- To ensure three properties concurrently, just use this combinator twice:+--+-- >	& foo `concurrently` bar `concurrently` baz+concurrently+	:: (IsProp p1, IsProp p2, Combines p1 p2, IsProp (CombinedType p1 p2))+	=> p1+	-> p2+	-> CombinedType p1 p2+concurrently p1 p2 = (combineWith go go p1 p2)+	`describe` d+  where+	d = getDesc p1 ++ " `concurrently` " ++ getDesc p2+	-- Increase the number of capabilities right up to the number of+	-- processors, so that A `concurrently` B `concurrently` C+	-- runs all 3 properties on different processors when possible.+	go a1 a2 = do+		n <- liftIO getNumProcessors+		withCapabilities n $+			concurrentSatisfy a1 a2++-- | Ensures all the properties in the list, with a specified amount of+-- concurrency.+-- +-- > concurrentList (pure 2) "demo" $ props+-- >	& foo+-- >	& bar+-- >	& baz+--+-- The above example will run foo and bar concurrently, and once either of+-- those 2 properties finishes, will start running baz.+concurrentList :: IO Int -> Desc -> PropList -> Property HasInfo+concurrentList getn d (PropList ps) = infoProperty d go mempty ps+  where+	go = do+		n <- liftIO getn+		withCapabilities n $+			startworkers n =<< liftIO (newMVar ps)+	startworkers n q+		| n < 1 = return NoChange+		| n == 1 = worker q NoChange+		| otherwise = +			worker q NoChange+				`concurrentSatisfy`+			startworkers (n-1) q+	worker q r = do+		v <- liftIO $ modifyMVar q $ \v -> case v of+			[] -> return ([], Nothing)+			(p:rest) -> return (rest, Just p)+		case v of+			Nothing -> return r+			-- This use of propertySatisfy does not lose any+			-- Info asociated with the property, because+			-- concurrentList sets all the properties as+			-- children, and so propigates their info.+			Just p -> do+				hn <- asks hostName+				r' <- actionMessageOn hn+					(propertyDesc p)+					(propertySatisfy p)+				worker q (r <> r')++-- | Run an action with the number of capabiities increased as necessary to+-- allow running on the specified number of cores.+--+-- Never increases the number of capabilities higher than the actual number+-- of processors.+withCapabilities :: Int -> Propellor a -> Propellor a+withCapabilities n a = bracket setup cleanup (const a)+  where+	setup = do+		np <- liftIO getNumProcessors+		let n' = min n np+		c <- liftIO getNumCapabilities+		when (n' > c) $ +			liftIO $ setNumCapabilities n'+		return c+	cleanup = liftIO . setNumCapabilities++-- | Running Propellor actions concurrently.+concurrentSatisfy :: Propellor Result -> Propellor Result -> Propellor Result+concurrentSatisfy a1 a2 = do+	h <- ask+	((r1, w1), (r2, w2)) <- liftIO $+		runp a1 h `A.concurrently` runp a2 h+	tell (w1 <> w2)+	return (r1 <> r2)+  where+	runp a h = evalRWST (runWithHost (catchPropellor a)) h ()
src/Propellor/Property/Conductor.hs view
@@ -83,7 +83,7 @@  -- | Class of things that can be conducted. class Conductable c where-	conducts :: c -> RevertableProperty+	conducts :: c -> RevertableProperty HasInfo  instance Conductable Host where 	-- | Conduct the specified host.@@ -268,7 +268,7 @@   where 	desc = "not " ++ cdesc (hostName h) -conductorKnownHost :: Host -> RevertableProperty+conductorKnownHost :: Host -> RevertableProperty NoInfo conductorKnownHost h =  	mk Ssh.knownHost 		<!>@@ -290,7 +290,7 @@ 	privinfo h' = forceHostContext (hostName h') $ getInfo (hostInfo h')  -- Use this property to let the specified conductor ssh in and run propellor.-conductedBy :: Host -> RevertableProperty+conductedBy :: Host -> RevertableProperty NoInfo conductedBy h = (setup <!> teardown) 	`describe` ("conducted by " ++ hostName h)   where
src/Propellor/Property/Debootstrap.hs view
@@ -98,7 +98,7 @@ -- When necessary, falls back to installing debootstrap from source. -- Note that installation from source is done by downloading the tarball -- from a Debian mirror, with no cryptographic verification.-installed :: RevertableProperty+installed :: RevertableProperty NoInfo installed = install <!> remove   where 	install = withOS "debootstrap installed" $ \o -> 
src/Propellor/Property/DiskImage.hs view
@@ -69,16 +69,16 @@ -- Note that the disk image file is reused if it already exists, -- to avoid expensive IO to generate a new one. And, it's updated in-place, -- so its contents are undefined during the build process.-imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty+imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty HasInfo imageBuilt = imageBuilt' False  -- | Like 'built', but the chroot is deleted and rebuilt from scratch each -- time. This is more expensive, but useful to ensure reproducible results -- when the properties of the chroot have been changed.-imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty+imageRebuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty HasInfo imageRebuilt = imageBuilt' True -imageBuilt' :: Bool -> DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty+imageBuilt' :: Bool -> DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty HasInfo imageBuilt' rebuild img mkchroot tabletype final partspec =  	imageBuiltFrom img chrootdir tabletype final partspec 		`requires` Chroot.provisioned chroot@@ -99,7 +99,7 @@ 		& Apt.cacheCleaned  -- | Builds a disk image from the contents of a chroot.-imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty+imageBuiltFrom :: DiskImage -> FilePath -> TableType -> Finalization -> [PartSpec] -> RevertableProperty NoInfo imageBuiltFrom img chrootdir tabletype final partspec = mkimg <!> rmimg   where 	desc = img ++ " built from " ++ chrootdir
src/Propellor/Property/Dns.hs view
@@ -60,7 +60,7 @@ -- -- In either case, the secondary dns server Host should have an ipv4 and/or -- ipv6 property defined.-primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty+primary :: [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty HasInfo primary hosts domain soa rs = setup <!> cleanup   where 	setup = setupPrimary zonefile id hosts domain soa rs@@ -152,7 +152,7 @@ -- This is different from the serial number used by 'primary', so if you -- want to later disable DNSSEC you will need to adjust the serial number -- passed to mkSOA to ensure it is larger.-signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty+signedPrimary :: Recurrance -> [Host] -> Domain -> SOA -> [(BindDomain, Record)] -> RevertableProperty HasInfo signedPrimary recurrance hosts domain soa rs = setup <!> cleanup   where 	setup = combineProperties ("dns primary for " ++ domain ++ " (signed)")@@ -164,7 +164,7 @@ 		`onChange` Service.reloaded "bind9"  	cleanup = cleanupPrimary zonefile domain-		`onChange` toProp (revert (zoneSigned domain zonefile))+		`onChange` revert (zoneSigned domain zonefile) 		`onChange` Service.reloaded "bind9"  	-- Include the public keys into the zone file.@@ -184,12 +184,12 @@ -- -- Note that if a host is declared to be a primary and a secondary dns -- server for the same domain, the primary server config always wins.-secondary :: [Host] -> Domain -> RevertableProperty+secondary :: [Host] -> Domain -> RevertableProperty HasInfo secondary hosts domain = secondaryFor (otherServers Master hosts domain) hosts domain  -- | This variant is useful if the primary server does not have its DNS -- configured via propellor.-secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty+secondaryFor :: [HostName] -> [Host] -> Domain -> RevertableProperty HasInfo secondaryFor masters hosts domain = setup <!> cleanup   where 	setup = pureInfoProperty desc (addNamedConf conf)
src/Propellor/Property/DnsSec.hs view
@@ -7,7 +7,7 @@ -- -- signedPrimary uses this, so this property does not normally need to be -- used directly.-keysInstalled :: Domain -> RevertableProperty+keysInstalled :: Domain -> RevertableProperty HasInfo keysInstalled domain = setup <!> cleanup   where 	setup = propertyList "DNSSEC keys installed" $@@ -37,15 +37,15 @@ -- -- signedPrimary uses this, so this property does not normally need to be -- used directly.-zoneSigned :: Domain -> FilePath -> RevertableProperty+zoneSigned :: Domain -> FilePath -> RevertableProperty HasInfo zoneSigned domain zonefile = setup <!> cleanup   where 	setup = check needupdate (forceZoneSigned domain zonefile)-		`requires` toProp (keysInstalled domain)+		`requires` keysInstalled domain 	 	cleanup = File.notPresent (signedZoneFile zonefile) 		`before` File.notPresent dssetfile-		`before` toProp (revert (keysInstalled domain))+		`before` revert (keysInstalled domain) 	 	dssetfile = dir </> "-" ++ domain ++ "." 	dir = takeDirectory zonefile
src/Propellor/Property/Docker.hs view
@@ -64,6 +64,7 @@ import Data.List hiding (init) import Data.List.Utils import qualified Data.Map as M+import System.Console.Concurrent  installed :: Property NoInfo installed = Apt.installed ["docker.io"]@@ -123,7 +124,7 @@ -- -- Reverting this property ensures that the container is stopped and -- removed.-docked :: Container -> RevertableProperty+docked :: Container -> RevertableProperty HasInfo docked ctr@(Container _ h) = 	(propagateContainerInfo ctr (go "docked" setup)) 		<!>@@ -540,6 +541,7 @@ 				warningMessage "Boot provision failed!" 		void $ async $ job reapzombies 		job $ do+			flushConcurrentOutput 			void $ tryIO $ ifM (inPath "bash") 				( boolSystem "bash" [Param "-l"] 				, boolSystem "/bin/sh" []@@ -555,7 +557,7 @@ provisionContainer cid = containerDesc cid $ property "provisioned" $ liftIO $ do 	let shim = Shim.file (localdir </> "propellor") (localdir </> shimdir cid) 	let params = ["--continue", show $ toChain cid]-	msgh <- mkMessageHandle+	msgh <- getMessageHandle 	let p = inContainerProcess cid 		(if isConsole msgh then ["-it"] else []) 		(shim : params)@@ -583,6 +585,7 @@ 			r <- runPropellor h $ ensureProperties $ 				map ignoreInfo $ 					hostProperties h+			flushConcurrentOutput 			putStrLn $ "\n" ++ show r  stopContainer :: ContainerId -> IO Bool
src/Propellor/Property/File.hs view
@@ -5,6 +5,7 @@  import System.Posix.Files import System.PosixCompat.Types+import System.Exit  type Line = String @@ -133,6 +134,27 @@ 			then noChange 			else makeChange updateLink 	updateLink = createSymbolicLink target `viaStableTmp` link++-- | Ensures that a file is a copy of another (regular) file.+isCopyOf :: FilePath -> FilePath -> Property NoInfo+f `isCopyOf` f' = property desc $ go =<< (liftIO $ tryIO $ getFileStatus f')+  where+	desc = f ++ " is copy of " ++ f'+	go (Right stat) = if isRegularFile stat+		then gocmp =<< (liftIO $ cmp)+		else warningMessage (f' ++ " is not a regular file") >>+			return FailedChange+	go (Left e) = warningMessage (show e) >> return FailedChange++	cmp = safeSystem "cmp" [Param "-s", Param "--", File f, File f']+	gocmp ExitSuccess = noChange+	gocmp (ExitFailure 1) = doit+	gocmp _ = warningMessage "cmp failed" >> return FailedChange++	doit = makeChange $ copy f' `viaStableTmp` f+	copy src dest = unlessM (runcp src dest) $ errorMessage "cp failed"+	runcp src dest = boolSystem "cp"+		[Param "--preserve=all", Param "--", File src, File dest]  -- | Ensures that a file/dir has the specified owner and group. ownerGroup :: FilePath -> User -> Group -> Property NoInfo
src/Propellor/Property/Git.hs view
@@ -11,7 +11,7 @@ -- using git-daemon, run from inetd. -- -- Note that reverting this property does not remove or stop inetd.-daemonRunning :: FilePath -> RevertableProperty+daemonRunning :: FilePath -> RevertableProperty NoInfo daemonRunning exportdir = setup <!> unsetup   where 	setup = containsLine conf (mkl "tcp4")
src/Propellor/Property/List.hs view
@@ -5,6 +5,7 @@ 	props, 	PropertyList(..), 	PropertyListType,+	PropList(..), ) where  import Propellor.Types
src/Propellor/Property/Nginx.hs view
@@ -9,7 +9,7 @@  type ConfigFile = [String] -siteEnabled :: HostName -> ConfigFile -> RevertableProperty+siteEnabled :: HostName -> ConfigFile -> RevertableProperty NoInfo siteEnabled hn cf = enable <!> disable   where 	enable = siteVal hn `File.isSymlinkedTo` siteValRelativeCfg hn
src/Propellor/Property/Prosody.hs view
@@ -11,7 +11,7 @@  type Conf = String -confEnabled :: Conf -> ConfigFile -> RevertableProperty+confEnabled :: Conf -> ConfigFile -> RevertableProperty NoInfo confEnabled conf cf = enable <!> disable   where 	enable = dir `File.isSymlinkedTo` target
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -298,7 +298,7 @@ 		, "  </Directory>" 		] -apacheSite :: HostName -> Bool -> Apache.ConfigFile -> RevertableProperty+apacheSite :: HostName -> Bool -> Apache.ConfigFile -> RevertableProperty NoInfo apacheSite hn withssl middle = Apache.siteEnabled hn $ apachecfg hn withssl middle  apachecfg :: HostName -> Bool -> Apache.ConfigFile -> Apache.ConfigFile@@ -738,7 +738,7 @@ -- This value can be included in a domain's additional records to make -- it use this domainkey. domainKey :: (BindDomain, Record)-domainKey = (RelDomain "mail._domainkey", TXT "v=DKIM1; k=rsa; t=y; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCc+/rfzNdt5DseBBmfB3C6sVM7FgVvf4h1FeCfyfwPpVcmPdW6M2I+NtJsbRkNbEICxiP6QY2UM0uoo9TmPqLgiCCG2vtuiG6XMsS0Y/gGwqKM7ntg/7vT1Go9vcquOFFuLa5PnzpVf8hB9+PMFdS4NPTvWL2c5xxshl/RJzICnQIDAQAB")+domainKey = (RelDomain "mail._domainkey", TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCc+/rfzNdt5DseBBmfB3C6sVM7FgVvf4h1FeCfyfwPpVcmPdW6M2I+NtJsbRkNbEICxiP6QY2UM0uoo9TmPqLgiCCG2vtuiG6XMsS0Y/gGwqKM7ntg/7vT1Go9vcquOFFuLa5PnzpVf8hB9+PMFdS4NPTvWL2c5xxshl/RJzICnQIDAQAB")  hasJoeyCAChain :: Property HasInfo hasJoeyCAChain = "/etc/ssl/certs/joeyca.pem" `File.hasPrivContentExposed`@@ -921,10 +921,10 @@ 		, "rewriterule (.*) http://joeyh.name$1 [r]" 		] -userDirHtml :: Property HasInfo+userDirHtml :: Property NoInfo userDirHtml = File.fileProperty "apache userdir is html" (map munge) conf 	`onChange` Apache.reloaded-	`requires` (toProp $ Apache.modEnabled "userdir")+	`requires` Apache.modEnabled "userdir"   where 	munge = replace "public_html" "html" 	conf = "/etc/apache2/mods-available/userdir.conf"
src/Propellor/Property/Ssh.hs view
@@ -115,7 +115,7 @@ -- ports it is configured to listen on. -- -- Revert to prevent it listening on a particular port.-listenPort :: Int -> RevertableProperty+listenPort :: Int -> RevertableProperty NoInfo listenPort port = enable <!> disable   where 	portline = "Port " ++ show port
src/Propellor/Property/Systemd.hs view
@@ -93,7 +93,7 @@ 	`describe` ("service " ++ n ++ " disabled")  -- | Masks a systemd service.-masked :: ServiceName -> RevertableProperty+masked :: ServiceName -> RevertableProperty NoInfo masked n = systemdMask <!> systemdUnmask   where 	systemdMask   = trivial $ cmdProperty "systemctl" ["mask", n]@@ -206,7 +206,7 @@ -- -- Reverting this property stops the container, removes the systemd unit, -- and deletes the chroot and all its contents.-nspawned :: Container -> RevertableProperty+nspawned :: Container -> RevertableProperty HasInfo nspawned c@(Container name (Chroot.Chroot loc builder _) h) = 	p `describe` ("nspawned " ++ name)   where@@ -231,7 +231,7 @@  -- | Sets up the service file for the container, and then starts -- it running.-nspawnService :: Container -> ChrootCfg -> RevertableProperty+nspawnService :: Container -> ChrootCfg -> RevertableProperty NoInfo nspawnService (Container name _ _) cfg = setup <!> teardown   where 	service = nspawnServiceName name@@ -282,7 +282,7 @@ -- -- This uses nsenter to enter the container, by looking up the pid of the -- container's init process and using its namespace.-enterScript :: Container -> RevertableProperty+enterScript :: Container -> RevertableProperty NoInfo enterScript c@(Container name _ _) = setup <!> teardown   where 	setup = combineProperties ("generated " ++ enterScriptFile c)@@ -328,7 +328,7 @@ -- When there is no leading dash, "--" is prepended to the parameter. -- -- Reverting the property will remove a parameter, if it's present.-containerCfg :: String -> RevertableProperty+containerCfg :: String -> RevertableProperty HasInfo containerCfg p = RevertableProperty (mk True) (mk False)   where 	mk b = pureInfoProperty ("container configuration " ++ (if b then "" else "without ") ++ p') $@@ -340,18 +340,18 @@ -- | Bind mounts </etc/resolv.conf> from the host into the container. -- -- This property is enabled by default. Revert it to disable it.-resolvConfed :: RevertableProperty+resolvConfed :: RevertableProperty HasInfo resolvConfed = containerCfg "bind=/etc/resolv.conf"  -- | Link the container's journal to the host's if possible. -- (Only works if the host has persistent journal enabled.) -- -- This property is enabled by default. Revert it to disable it.-linkJournal :: RevertableProperty+linkJournal :: RevertableProperty HasInfo linkJournal = containerCfg "link-journal=try-guest"  -- | Disconnect networking of the container from the host.-privateNetwork :: RevertableProperty+privateNetwork :: RevertableProperty HasInfo privateNetwork = containerCfg "private-network"  class Publishable a where@@ -389,7 +389,7 @@ -- >	& Systemd.running Systemd.networkd -- >	& Systemd.publish (Port 80 ->- Port 8080) -- >	& Apt.installedRunning "apache2"-publish :: Publishable p => p -> RevertableProperty+publish :: Publishable p => p -> RevertableProperty HasInfo publish p = containerCfg $ "--port=" ++ toPublish p  class Bindable a where@@ -402,9 +402,9 @@ 	toBind v = hostSide v ++ ":" ++ containerSide v  -- | Bind mount a file or directory from the host into the container.-bind :: Bindable p => p -> RevertableProperty+bind :: Bindable p => p -> RevertableProperty HasInfo bind p = containerCfg $ "--bind=" ++ toBind p  -- | Read-only mind mount.-bindRo :: Bindable p => p -> RevertableProperty+bindRo :: Bindable p => p -> RevertableProperty HasInfo bindRo p = containerCfg $ "--bind-ro=" ++ toBind p
src/Propellor/Property/Uwsgi.hs view
@@ -11,7 +11,7 @@  type AppName = String -appEnabled :: AppName -> ConfigFile -> RevertableProperty+appEnabled :: AppName -> ConfigFile -> RevertableProperty NoInfo appEnabled an cf = enable <!> disable   where 	enable = appVal an `File.isSymlinkedTo` appValRelativeCfg an
src/Propellor/Spin.hs view
@@ -33,7 +33,8 @@ commitSpin :: IO () commitSpin = do 	void $ actionMessage "Git commit" $-		gitCommit [Param "--allow-empty", Param "-a", Param "-m", Param spinCommitMessage]+		gitCommit (Just spinCommitMessage) +			[Param "--allow-empty", Param "-a"] 	-- Push to central origin repo first, if possible. 	-- The remote propellor will pull from there, which avoids 	-- us needing to send stuff directly to the remote host.@@ -60,7 +61,7 @@ 	updateServer target relay hst 		(proc "ssh" $ cacheparams ++ [sshtarget, shellWrap probecmd]) 		(proc "ssh" $ cacheparams ++ [sshtarget, shellWrap updatecmd])-		getprivdata+		=<< getprivdata  	-- And now we can run it. 	unlessM (boolSystem "ssh" (map Param $ cacheparams ++ ["-t", sshtarget, shellWrap runcmd])) $@@ -189,16 +190,16 @@ 	-> Host 	-> CreateProcess 	-> CreateProcess-	-> IO PrivMap+	-> PrivMap 	-> IO ()-updateServer target relay hst connect haveprecompiled getprivdata =+updateServer target relay hst connect haveprecompiled privdata = 	withIOHandles createProcessSuccess connect go   where 	hn = fromMaybe target relay  	go (toh, fromh) = do 		let loop = go (toh, fromh)-		let restart = updateServer hn relay hst connect haveprecompiled getprivdata+		let restart = updateServer hn relay hst connect haveprecompiled privdata 		let done = return () 		v <- maybe Nothing readish <$> getMarked fromh statusMarker 		case v of@@ -206,7 +207,7 @@ 				sendRepoUrl toh 				loop 			(Just NeedPrivData) -> do-				sendPrivData hn toh =<< getprivdata+				sendPrivData hn toh privdata 				loop 			(Just NeedGitClone) -> do 				hClose toh@@ -217,7 +218,7 @@ 				hClose toh 				hClose fromh 				sendPrecompiled hn-				updateServer hn relay hst haveprecompiled (error "loop") getprivdata+				updateServer hn relay hst haveprecompiled (error "loop") privdata 			(Just NeedGitPush) -> do 				sendGitUpdate hn fromh toh 				hClose fromh@@ -336,8 +337,9 @@ 	old_head <- getCurrentGitSha1 branch 	old_commit <- findLastNonSpinCommit 	rungit "reset" [Param old_commit]-	rungit "commit" [Param "-a", Param "--allow-empty"]-	rungit "merge" =<< gpgSignParams [Param "-s", Param "ours", Param old_head]+	unlessM (gitCommit Nothing [Param "-a", Param "--allow-empty"]) $+		error "git commit failed"+	rungit "merge" =<< gpgSignParams [Param "-s", Param "ours", Param old_head, Param "--no-edit"] 	current_commit <- getCurrentGitSha1 branch 	rungit "update-ref" [Param branchref, Param current_commit] 	rungit "checkout" [Param branch]
src/Propellor/Types.hs view
@@ -27,7 +27,6 @@ 	, IsProp(..) 	, Combines(..) 	, CombinedType-	, combineWith 	, Propellor(..) 	, LiftPropellor(..) 	, EndAction(..)@@ -157,9 +156,6 @@ propertySatisfy (IProperty _ a _ _) = a propertySatisfy (SProperty _ a _) = a -instance Show (Property i) where-        show p = "property " ++ show (propertyDesc p)- -- | Changes the action that is performed to satisfy a property.  adjustPropertySatisfy :: Property i -> (Propellor Result -> Propellor Result) -> Property i adjustPropertySatisfy (IProperty d s i cs) f = IProperty d (f s) i cs@@ -173,6 +169,9 @@ propertyDesc (IProperty d _ _ _) = d propertyDesc (SProperty d _ _) = d +instance Show (Property i) where+        show p = "property " ++ show (propertyDesc p)+ -- | A Property can include a list of child properties that it also -- satisfies. This allows them to be introspected to collect their info, etc. propertyChildren :: Property i -> [Property i]@@ -181,12 +180,24 @@  -- | A property that can be reverted. The first Property is run -- normally and the second is run when it's reverted.-data RevertableProperty = RevertableProperty (Property HasInfo) (Property HasInfo)+data RevertableProperty i = RevertableProperty (Property i) (Property i) --- | Shorthand to construct a revertable property.-(<!>) :: Property i1 -> Property i2 -> RevertableProperty-p1 <!> p2 = RevertableProperty (toIProperty p1) (toIProperty p2)+instance Show (RevertableProperty i) where+        show (RevertableProperty p _) = show p +class MkRevertableProperty i1 i2 where+	-- | Shorthand to construct a revertable property.+	(<!>) :: Property i1 -> Property i2 -> RevertableProperty (CInfo i1 i2)++instance MkRevertableProperty HasInfo HasInfo where+	x <!> y = RevertableProperty x y+instance MkRevertableProperty NoInfo NoInfo where+	x <!> y = RevertableProperty x y+instance MkRevertableProperty NoInfo HasInfo where+	x <!> y = RevertableProperty (toProp x) y+instance MkRevertableProperty HasInfo NoInfo where+	x <!> y = RevertableProperty x (toProp y)+ -- | Class of types that can be used as properties of a host. class IsProp p where 	setDesc :: p -> Desc -> p@@ -208,70 +219,116 @@ 	getDesc = propertyDesc 	getInfoRecursive _ = mempty -instance IsProp RevertableProperty where-	-- | Sets the description of both sides.-	setDesc (RevertableProperty p1 p2) d = -		RevertableProperty (setDesc p1 d) (setDesc p2 ("not " ++ d))+instance IsProp (RevertableProperty HasInfo) where+	setDesc = setDescR 	getDesc (RevertableProperty p1 _) = getDesc p1 	toProp (RevertableProperty p1 _) = p1 	-- | Return the Info of the currently active side. 	getInfoRecursive (RevertableProperty p1 _p2) = getInfoRecursive p1+instance IsProp (RevertableProperty NoInfo) where+	setDesc = setDescR+	getDesc (RevertableProperty p1 _) = getDesc p1+	toProp (RevertableProperty p1 _) = toProp p1+	getInfoRecursive (RevertableProperty _ _) = mempty +-- | Sets the description of both sides.+setDescR :: IsProp (Property i) => RevertableProperty i -> Desc -> RevertableProperty i+setDescR (RevertableProperty p1 p2) d =+	RevertableProperty (setDesc p1 d) (setDesc p2 ("not " ++ d))+ -- | Type level calculation of the type that results from combining two -- types of properties. type family CombinedType x y type instance CombinedType (Property x) (Property y) = Property (CInfo x y)-type instance CombinedType RevertableProperty (Property NoInfo) = RevertableProperty-type instance CombinedType RevertableProperty (Property HasInfo) = RevertableProperty-type instance CombinedType RevertableProperty RevertableProperty = RevertableProperty+type instance CombinedType (RevertableProperty x) (RevertableProperty y) = RevertableProperty (CInfo x y)+-- When only one of the properties is revertable, the combined property is+-- not fully revertable, so is not a RevertableProperty.+type instance CombinedType (RevertableProperty x) (Property y) = Property (CInfo x y)+type instance CombinedType (Property x) (RevertableProperty y) = Property (CInfo x y) -class Combines x y where-	-- | Combines two properties. The second property is ensured-	-- first, and only once it is successfully ensures will the first-	-- be ensured. The combined property will have the description of-	-- the first property.-	(<<>>) :: x -> y -> CombinedType x y+type ResultCombiner = Propellor Result -> Propellor Result -> Propellor Result --- | Combines together two properties, yielding a property that--- has the description and info of the first, and that has the second--- property as a child. The two actions to satisfy the properties--- are passed to a function that can combine them in arbitrary ways.-combineWith-	:: (Combines (Property x) (Property y))-	=> (Propellor Result -> Propellor Result -> Propellor Result)-	-> Property x-	-> Property y-	-> CombinedType (Property x) (Property y)-combineWith f x y = adjustPropertySatisfy (x <<>> y) $ \_ ->-	f (propertySatisfy $ toSProperty x) (propertySatisfy $ toSProperty y)+class Combines x y where+	-- | Combines together two properties, yielding a property that+	-- has the description and info of the first, and that has the second+	-- property as a child. +	combineWith +		:: ResultCombiner+		-- ^ How to combine the actions to satisfy the properties.+		-> ResultCombiner+		-- ^ Used when combining revertable properties, to combine+		-- their reversion actions.+		-> x+		-> y+		-> CombinedType x y  instance Combines (Property HasInfo) (Property HasInfo) where-	(IProperty d1 a1 i1 cs1) <<>> y@(IProperty _d2 a2 _i2 _cs2) =-		IProperty d1 (a2 <> a1) i1 (y : cs1)+	combineWith f _ (IProperty d1 a1 i1 cs1) y@(IProperty _d2 a2 _i2 _cs2) =+		IProperty d1 (f a1 a2) i1 (y : cs1)  instance Combines (Property HasInfo) (Property NoInfo) where-	(IProperty d1 a1 i1 cs1) <<>> y@(SProperty _d2 a2 _cs2) =-		IProperty d1 (a2 <> a1) i1 (toIProperty y : cs1)+	combineWith f _ (IProperty d1 a1 i1 cs1) y@(SProperty _d2 a2 _cs2) =+		IProperty d1 (f a1 a2) i1 (toIProperty y : cs1)  instance Combines (Property NoInfo) (Property HasInfo) where-	(SProperty d1 a1 cs1) <<>> y@(IProperty _d2 a2 _i2 _cs2) =-		IProperty d1 (a2 <> a1) mempty (y : map toIProperty cs1)+	combineWith f _ (SProperty d1 a1 cs1) y@(IProperty _d2 a2 _i2 _cs2) =+		IProperty d1 (f a1 a2) mempty (y : map toIProperty cs1)  instance Combines (Property NoInfo) (Property NoInfo) where-	(SProperty d1 a1  cs1) <<>> y@(SProperty _d2 a2 _cs2) =-		SProperty d1 (a2 <> a1) (y : cs1)+	combineWith f _ (SProperty d1 a1  cs1) y@(SProperty _d2 a2 _cs2) =+		SProperty d1 (f a1 a2) (y : cs1) -instance Combines RevertableProperty (Property HasInfo) where-	(RevertableProperty p1 p2) <<>> y =-		RevertableProperty (p1 <<>> y) p2+instance Combines (RevertableProperty NoInfo) (RevertableProperty NoInfo) where+	combineWith = combineWithRR+instance Combines (RevertableProperty HasInfo) (RevertableProperty HasInfo) where+	combineWith = combineWithRR+instance Combines (RevertableProperty HasInfo) (RevertableProperty NoInfo) where+	combineWith = combineWithRR+instance Combines (RevertableProperty NoInfo) (RevertableProperty HasInfo) where+	combineWith = combineWithRR+instance Combines (RevertableProperty NoInfo) (Property HasInfo) where+	combineWith = combineWithRP+instance Combines (RevertableProperty NoInfo) (Property NoInfo) where+	combineWith = combineWithRP+instance Combines (RevertableProperty HasInfo) (Property HasInfo) where+	combineWith = combineWithRP+instance Combines (RevertableProperty HasInfo) (Property NoInfo) where+	combineWith = combineWithRP+instance Combines (Property HasInfo) (RevertableProperty NoInfo) where+	combineWith = combineWithPR+instance Combines (Property NoInfo) (RevertableProperty NoInfo) where+	combineWith = combineWithPR+instance Combines (Property HasInfo) (RevertableProperty HasInfo) where+	combineWith = combineWithPR+instance Combines (Property NoInfo) (RevertableProperty HasInfo) where+	combineWith = combineWithPR -instance Combines RevertableProperty (Property NoInfo) where-	(RevertableProperty p1 p2) <<>> y =-		RevertableProperty (p1 <<>> toIProperty y) p2+combineWithRR +	:: Combines (Property x) (Property y)+	=> ResultCombiner+	-> ResultCombiner+	-> RevertableProperty x+	-> RevertableProperty y+	-> RevertableProperty (CInfo x y)+combineWithRR sf tf (RevertableProperty s1 t1) (RevertableProperty s2 t2) =+	RevertableProperty+		(combineWith sf tf s1 s2)+		(combineWith tf sf t1 t2) -instance Combines RevertableProperty RevertableProperty where-	(RevertableProperty x1 x2) <<>> (RevertableProperty y1 y2) =-		RevertableProperty-			(x1 <<>> y1)-			-- when reverting, run actions in reverse order-			(y2 <<>> x2)+combineWithRP+	:: Combines (Property i) y+	=> (Propellor Result -> Propellor Result -> Propellor Result)+	-> (Propellor Result -> Propellor Result -> Propellor Result)+	-> RevertableProperty i+	-> y+	-> CombinedType (Property i) y+combineWithRP sf tf (RevertableProperty x _) y = combineWith sf tf x y++combineWithPR+	:: Combines x (Property i)+	=> (Propellor Result -> Propellor Result -> Propellor Result)+	-> (Propellor Result -> Propellor Result -> Propellor Result)+	-> x+	-> RevertableProperty i+	-> CombinedType x (Property i)+combineWithPR sf tf x (RevertableProperty y _) = combineWith sf tf x y
+ src/System/Console/Concurrent.hs view
@@ -0,0 +1,44 @@+-- | +-- Copyright: 2015 Joey Hess <id@joeyh.name>+-- License: BSD-2-clause+-- +-- Concurrent output handling.+--+-- > import Control.Concurrent.Async+-- > import System.Console.Concurrent+-- >+-- > main = withConcurrentOutput $+-- > 	outputConcurrent "washed the car\n"+-- > 		`concurrently`+-- >	outputConcurrent "walked the dog\n"+-- >		`concurrently`+-- > 	createProcessConcurrent (proc "ls" [])++{-# LANGUAGE CPP #-}++module System.Console.Concurrent (+	-- * Concurrent output+	withConcurrentOutput,+	Outputable(..),+	outputConcurrent,+	errorConcurrent,+	ConcurrentProcessHandle,+#ifndef mingw32_HOST_OS+	createProcessConcurrent,+#endif+	waitForProcessConcurrent,+	createProcessForeground,+	flushConcurrentOutput,+	lockOutput,+	-- * Low level access to the output buffer+	OutputBuffer,+	StdHandle(..),+	bufferOutputSTM,+	outputBufferWaiterSTM,+	waitAnyBuffer,+	waitCompleteLines,+	emitOutputBuffer,+) where++import System.Console.Concurrent.Internal+
+ src/System/Console/Concurrent/Internal.hs view
@@ -0,0 +1,538 @@+{-# LANGUAGE BangPatterns, TypeSynonymInstances, FlexibleInstances, TupleSections #-}+{-# LANGUAGE CPP #-}++-- | +-- Copyright: 2015 Joey Hess <id@joeyh.name>+-- License: BSD-2-clause+-- +-- Concurrent output handling, internals.+--+-- May change at any time.++module System.Console.Concurrent.Internal where++import System.IO+#ifndef mingw32_HOST_OS+import System.Posix.IO+#endif+import System.Directory+import System.Exit+import Control.Monad+import Control.Monad.IO.Class (liftIO, MonadIO)+import Control.Applicative+import System.IO.Unsafe (unsafePerformIO)+import Control.Concurrent+import Control.Concurrent.STM+import Control.Concurrent.Async+import Data.Maybe+import Data.List+import Data.Monoid+import qualified System.Process as P+import qualified Data.Text as T+import qualified Data.Text.IO as T++import Utility.Monad+import Utility.Exception++data OutputHandle = OutputHandle+	{ outputLock :: TMVar Lock+	, outputBuffer :: TMVar OutputBuffer+	, errorBuffer :: TMVar OutputBuffer+	, outputThreads :: TMVar Integer+	, processWaiters :: TMVar [Async ()]+	, waitForProcessLock :: TMVar ()+	}++data Lock = Locked++-- | A shared global variable for the OutputHandle.+{-# NOINLINE globalOutputHandle #-}+globalOutputHandle :: OutputHandle+globalOutputHandle = unsafePerformIO $ OutputHandle+	<$> newEmptyTMVarIO+	<*> newTMVarIO (OutputBuffer [])+	<*> newTMVarIO (OutputBuffer [])+	<*> newTMVarIO 0+	<*> newTMVarIO []+	<*> newEmptyTMVarIO++-- | Holds a lock while performing an action. This allows the action to+-- perform its own output to the console, without using functions from this+-- module.+--+-- While this is running, other threads that try to lockOutput will block.+-- Any calls to `outputConcurrent` and `createProcessConcurrent` will not+-- block, but the output will be buffered and displayed only once the+-- action is done.+lockOutput :: (MonadIO m, MonadMask m) => m a -> m a+lockOutput = bracket_ (liftIO takeOutputLock) (liftIO dropOutputLock)++-- | Blocks until we have the output lock.+takeOutputLock :: IO ()+takeOutputLock = void $ takeOutputLock' True++-- | Tries to take the output lock, without blocking.+tryTakeOutputLock :: IO Bool+tryTakeOutputLock = takeOutputLock' False++withLock :: (TMVar Lock -> STM a) -> IO a+withLock a = atomically $ a (outputLock globalOutputHandle)++takeOutputLock' :: Bool -> IO Bool+takeOutputLock' block = do+	locked <- withLock $ \l -> do+		v <- tryTakeTMVar l+		case v of+			Just Locked+				| block -> retry+				| otherwise -> do+					-- Restore value we took.+					putTMVar l Locked+					return False+			Nothing -> do+				putTMVar l Locked+				return True+	when locked $ do+		(outbuf, errbuf) <- atomically $ (,)+			<$> swapTMVar (outputBuffer globalOutputHandle) (OutputBuffer [])+			<*> swapTMVar (errorBuffer globalOutputHandle) (OutputBuffer [])+		emitOutputBuffer StdOut outbuf+		emitOutputBuffer StdErr errbuf+	return locked++-- | Only safe to call after taking the output lock.+dropOutputLock :: IO ()+dropOutputLock = withLock $ void . takeTMVar++-- | Use this around any actions that use `outputConcurrent`+-- or `createProcessConcurrent`+--+-- This is necessary to ensure that buffered concurrent output actually+-- gets displayed before the program exits.+withConcurrentOutput :: (MonadIO m, MonadMask m) => m a -> m a+withConcurrentOutput a = a `finally` liftIO flushConcurrentOutput++-- | Blocks until any processes started by `createProcessConcurrent` have+-- finished, and any buffered output is displayed. +--+-- `withConcurrentOutput` calls this at the end; you can call it anytime+-- you want to flush output.+flushConcurrentOutput :: IO ()+flushConcurrentOutput = do+	-- Wait for all outputThreads to finish.+	let v = outputThreads globalOutputHandle+	atomically $ do+		r <- takeTMVar v+		if r <= 0+			then putTMVar v r+			else retry+	-- Take output lock to ensure that nothing else is currently+	-- generating output, and flush any buffered output.+	lockOutput $ return ()++-- | Values that can be output.+class Outputable v where+	toOutput :: v -> T.Text++instance Outputable T.Text where+	toOutput = id++instance Outputable String where+	toOutput = toOutput . T.pack++-- | Displays a value to stdout.+--+-- No newline is appended to the value, so if you want a newline, be sure+-- to include it yourself.+--+-- Uses locking to ensure that the whole output occurs atomically+-- even when other threads are concurrently generating output.+--+-- When something else is writing to the console at the same time, this does+-- not block. It buffers the value, so it will be displayed once the other+-- writer is done.+outputConcurrent :: Outputable v => v -> IO ()+outputConcurrent = outputConcurrent' StdOut++-- | Like `outputConcurrent`, but displays to stderr.+--+-- (Does not throw an exception.)+errorConcurrent :: Outputable v => v -> IO ()+errorConcurrent = outputConcurrent' StdErr++outputConcurrent' :: Outputable v => StdHandle -> v -> IO ()+outputConcurrent' stdh v = bracket setup cleanup go+  where+	setup = tryTakeOutputLock+	cleanup False = return ()+	cleanup True = dropOutputLock+	go True = do+		T.hPutStr h (toOutput v)+		hFlush h+	go False = do+		oldbuf <- atomically $ takeTMVar bv+		newbuf <- addOutputBuffer (Output (toOutput v)) oldbuf+		atomically $ putTMVar bv newbuf+	h = toHandle stdh+	bv = bufferFor stdh++newtype ConcurrentProcessHandle = ConcurrentProcessHandle P.ProcessHandle++toConcurrentProcessHandle :: (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle) -> (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)+toConcurrentProcessHandle (i, o, e, h) = (i, o, e, ConcurrentProcessHandle h)++-- | Use this to wait for processes started with +-- `createProcessConcurrent` and `createProcessForeground`, and get their+-- exit status.+--+-- Note that such processes are actually automatically waited for+-- internally, so not calling this explicitly will not result+-- in zombie processes. This behavior differs from `P.waitForProcess`+waitForProcessConcurrent :: ConcurrentProcessHandle -> IO ExitCode+waitForProcessConcurrent (ConcurrentProcessHandle h) = +	bracket lock unlock checkexit+  where+	lck = waitForProcessLock globalOutputHandle+	lock = atomically $ tryPutTMVar lck ()+	unlock True = atomically $ takeTMVar lck+	unlock False = return ()+	checkexit locked = maybe (waitsome locked) return+		=<< P.getProcessExitCode h+	waitsome True = do+		let v = processWaiters globalOutputHandle+		l <- atomically $ readTMVar v+		if null l+			-- Avoid waitAny [] which blocks forever+			then P.waitForProcess h+			else do+				-- Wait for any of the running+				-- processes to exit. It may or may not+				-- be the one corresponding to the+				-- ProcessHandle. If it is,+				-- getProcessExitCode will succeed.+				void $ tryIO $ waitAny l+				checkexit True+	waitsome False = do+		-- Another thread took the lck first. Wait for that thread to+		-- wait for one of the running processes to exit.+		atomically $ do+			putTMVar lck ()+			takeTMVar lck+		checkexit False++-- Registers an action that waits for a process to exit,+-- adding it to the processWaiters list, and removing it once the action+-- completes.+asyncProcessWaiter :: IO () -> IO ()+asyncProcessWaiter waitaction = do+	regdone <- newEmptyTMVarIO+	waiter <- async $ do+		self <- atomically (takeTMVar regdone)+		waitaction `finally` unregister self+	register waiter regdone+  where+	v = processWaiters globalOutputHandle+  	register waiter regdone = atomically $ do+		l <- takeTMVar v+		putTMVar v (waiter:l)+		putTMVar regdone waiter+	unregister waiter = atomically $ do+		l <- takeTMVar v+		putTMVar v (filter (/= waiter) l)++-- | Wrapper around `System.Process.createProcess` that prevents +-- multiple processes that are running concurrently from writing+-- to stdout/stderr at the same time.+--+-- If the process does not output to stdout or stderr, it's run+-- by createProcess entirely as usual. Only processes that can generate+-- output are handled specially:+--+-- A process is allowed to write to stdout and stderr in the usual+-- way, assuming it can successfully take the output lock.+--+-- When the output lock is held (ie, by another concurrent process,+-- or because `outputConcurrent` is being called at the same time),+-- the process is instead run with its stdout and stderr+-- redirected to a buffer. The buffered output will be displayed as soon+-- as the output lock becomes free.+--+-- Currently only available on Unix systems, not Windows.+#ifndef mingw32_HOST_OS+createProcessConcurrent :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle) +createProcessConcurrent p+	| willOutput (P.std_out p) || willOutput (P.std_err p) =+		ifM tryTakeOutputLock+			( fgProcess p+			, bgProcess p+			)+	| otherwise = do+		r@(_, _, _, h) <- P.createProcess p+		asyncProcessWaiter $+			void $ tryIO $ P.waitForProcess h+		return (toConcurrentProcessHandle r)+#endif++-- | Wrapper around `System.Process.createProcess` that makes sure a process+-- is run in the foreground, with direct access to stdout and stderr.+-- Useful when eg, running an interactive process.+createProcessForeground :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)+createProcessForeground p = do+	takeOutputLock+	fgProcess p++fgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)+fgProcess p = do+	r@(_, _, _, h) <- P.createProcess p+		`onException` dropOutputLock+	-- Wait for the process to exit and drop the lock.+	asyncProcessWaiter $ do+		void $ tryIO $ P.waitForProcess h+		dropOutputLock+	return (toConcurrentProcessHandle r)++#ifndef mingw32_HOST_OS+bgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)+bgProcess p = do+	(toouth, fromouth) <- pipe+	(toerrh, fromerrh) <- pipe+	let p' = p+		{ P.std_out = rediroutput (P.std_out p) toouth+		, P.std_err = rediroutput (P.std_err p) toerrh+		}+	registerOutputThread+	r@(_, _, _, h) <- P.createProcess p'+		`onException` unregisterOutputThread+	asyncProcessWaiter $ void $ tryIO $ P.waitForProcess h+	outbuf <- setupOutputBuffer StdOut toouth (P.std_out p) fromouth+	errbuf <- setupOutputBuffer StdErr toerrh (P.std_err p) fromerrh+	void $ async $ bufferWriter [outbuf, errbuf]+	return (toConcurrentProcessHandle r)+  where+	pipe = do+		(from, to) <- createPipe+		(,) <$> fdToHandle to <*> fdToHandle from+	rediroutput ss h+		| willOutput ss = P.UseHandle h+		| otherwise = ss+#endif++willOutput :: P.StdStream -> Bool+willOutput P.Inherit = True+willOutput _ = False++-- | Buffered output.+data OutputBuffer = OutputBuffer [OutputBufferedActivity]+	deriving (Eq)++data StdHandle = StdOut | StdErr++toHandle :: StdHandle -> Handle+toHandle StdOut = stdout+toHandle StdErr = stderr++bufferFor :: StdHandle -> TMVar OutputBuffer+bufferFor StdOut = outputBuffer globalOutputHandle+bufferFor StdErr = errorBuffer globalOutputHandle++data OutputBufferedActivity+	= Output T.Text+	| InTempFile+		{ tempFile :: FilePath+		, endsInNewLine :: Bool+		}+	deriving (Eq)++data AtEnd = AtEnd+	deriving Eq++data BufSig = BufSig++setupOutputBuffer :: StdHandle -> Handle -> P.StdStream -> Handle -> IO (StdHandle, MVar OutputBuffer, TMVar BufSig, TMVar AtEnd)+setupOutputBuffer h toh ss fromh = do+	hClose toh+	buf <- newMVar (OutputBuffer [])+	bufsig <- atomically newEmptyTMVar+	bufend <- atomically newEmptyTMVar+	void $ async $ outputDrainer ss fromh buf bufsig bufend+	return (h, buf, bufsig, bufend)++-- Drain output from the handle, and buffer it.+outputDrainer :: P.StdStream -> Handle -> MVar OutputBuffer -> TMVar BufSig -> TMVar AtEnd -> IO ()+outputDrainer ss fromh buf bufsig bufend+	| willOutput ss = go+	| otherwise = atend+  where+	go = do+		t <- T.hGetChunk fromh+		if T.null t+			then atend+			else do+				modifyMVar_ buf $ addOutputBuffer (Output t)+				changed+				go+	atend = do+		atomically $ putTMVar bufend AtEnd+		hClose fromh+	changed = atomically $ do+		void $ tryTakeTMVar bufsig+		putTMVar bufsig BufSig++registerOutputThread :: IO ()+registerOutputThread = do+	let v = outputThreads globalOutputHandle+	atomically $ putTMVar v . succ =<< takeTMVar v+	+unregisterOutputThread :: IO ()+unregisterOutputThread = do+	let v = outputThreads globalOutputHandle+	atomically $ putTMVar v . pred =<< takeTMVar v++-- Wait to lock output, and once we can, display everything +-- that's put into the buffers, until the end.+--+-- If end is reached before lock is taken, instead add the command's+-- buffers to the global outputBuffer and errorBuffer.+bufferWriter :: [(StdHandle, MVar OutputBuffer, TMVar BufSig, TMVar AtEnd)] -> IO ()+bufferWriter ts = do+	activitysig <- atomically newEmptyTMVar+	worker1 <- async $ lockOutput $+		ifM (atomically $ tryPutTMVar activitysig ())+			( void $ mapConcurrently displaybuf ts+			, noop -- buffers already moved to global+			)+	worker2 <- async $ void $ globalbuf activitysig+	void $ async $ do+		void $ waitCatch worker1+		void $ waitCatch worker2+		unregisterOutputThread+  where+	displaybuf v@(outh, buf, bufsig, bufend) = do+		change <- atomically $+			(Right <$> takeTMVar bufsig)+				`orElse`+			(Left <$> takeTMVar bufend)+		l <- takeMVar buf+		putMVar buf (OutputBuffer [])+		emitOutputBuffer outh l+		case change of+			Right BufSig -> displaybuf v+			Left AtEnd -> return ()+	globalbuf activitysig = do+		ok <- atomically $ do+			-- signal we're going to handle it+			-- (returns false if the displaybuf already did)+			ok <- tryPutTMVar activitysig ()+			-- wait for end of all buffers+			when ok $+				mapM_ (\(_outh, _buf, _bufsig, bufend) -> takeTMVar bufend) ts+			return ok+		when ok $ do+			-- add all of the command's buffered output to the+			-- global output buffer, atomically+			bs <- forM ts $ \(outh, buf, _bufsig, _bufend) ->+				(outh,) <$> takeMVar buf+			atomically $+				forM_ bs $ \(outh, b) -> +					bufferOutputSTM' outh b++-- Adds a value to the OutputBuffer. When adding Output to a Handle,+-- it's cheaper to combine it with any already buffered Output to that+-- same Handle.+--+-- When the total buffered Output exceeds 1 mb in size, it's moved out of+-- memory, to a temp file. This should only happen rarely, but is done to+-- avoid some verbose process unexpectedly causing excessive memory use.+addOutputBuffer :: OutputBufferedActivity -> OutputBuffer -> IO OutputBuffer+addOutputBuffer (Output t) (OutputBuffer buf)+	| T.length t' <= 1048576 = return $ OutputBuffer (Output t' : other)+	| otherwise = do+		tmpdir <- getTemporaryDirectory+		(tmp, h) <- openTempFile tmpdir "output.tmp"+		let !endnl = endsNewLine t'+		let i = InTempFile+			{ tempFile = tmp+			, endsInNewLine = endnl+			}+		T.hPutStr h t'+		hClose h+		return $ OutputBuffer (i : other)+  where+	!t' = T.concat (mapMaybe getOutput this) <> t+	!(this, other) = partition isOutput buf+	isOutput v = case v of+		Output _ -> True+		_ -> False+	getOutput v = case v of+		Output t'' -> Just t''+		_ -> Nothing+addOutputBuffer v (OutputBuffer buf) = return $ OutputBuffer (v:buf)++-- | Adds a value to the output buffer for later display.+--+-- Note that buffering large quantities of data this way will keep it+-- resident in memory until it can be displayed. While `outputConcurrent`+-- uses temp files if the buffer gets too big, this STM function cannot do+-- so.+bufferOutputSTM :: Outputable v => StdHandle -> v -> STM ()+bufferOutputSTM h v = bufferOutputSTM' h (OutputBuffer [Output (toOutput v)])++bufferOutputSTM' :: StdHandle -> OutputBuffer -> STM ()+bufferOutputSTM' h (OutputBuffer newbuf) = do+	(OutputBuffer buf) <- takeTMVar bv+	putTMVar bv (OutputBuffer (newbuf ++ buf))+  where+	bv = bufferFor h++-- | A STM action that waits for some buffered output to become+-- available, and returns it.+--+-- The function can select a subset of output when only some is desired;+-- the fst part is returned and the snd is left in the buffer.+--+-- This will prevent it from being displayed in the usual way, so you'll+-- need to use `emitOutputBuffer` to display it yourself.+outputBufferWaiterSTM :: (OutputBuffer -> (OutputBuffer, OutputBuffer)) -> STM (StdHandle, OutputBuffer)+outputBufferWaiterSTM selector = waitgetbuf StdOut `orElse` waitgetbuf StdErr+  where+	waitgetbuf h = do+		let bv = bufferFor h+		(selected, rest) <- selector <$> takeTMVar bv+		when (selected == OutputBuffer [])+			retry+		putTMVar bv rest+		return (h, selected)++waitAnyBuffer :: OutputBuffer -> (OutputBuffer, OutputBuffer)+waitAnyBuffer b = (b, OutputBuffer [])++-- | Use with `outputBufferWaiterSTM` to make it only return buffered+-- output that ends with a newline. Anything buffered without a newline+-- is left in the buffer.+waitCompleteLines :: OutputBuffer -> (OutputBuffer, OutputBuffer)+waitCompleteLines (OutputBuffer l) = +	let (selected, rest) = span completeline l+	in (OutputBuffer selected, OutputBuffer rest)+  where+	completeline (v@(InTempFile {})) = endsInNewLine v+	completeline (Output b) = endsNewLine b++endsNewLine :: T.Text -> Bool+endsNewLine t = not (T.null t) && T.last t == '\n'++-- | Emits the content of the OutputBuffer to the Handle+--+-- If you use this, you should use `lockOutput` to ensure you're the only+-- thread writing to the console.+emitOutputBuffer :: StdHandle -> OutputBuffer -> IO ()+emitOutputBuffer stdh (OutputBuffer l) = +	forM_ (reverse l) $ \ba -> case ba of+		Output t -> emit t+		InTempFile tmp _ -> do+			emit =<< T.readFile tmp+			void $ tryWhenExists $ removeFile tmp+  where+	outh = toHandle stdh+	emit t = void $ tryIO $ do+		T.hPutStr outh t+		hFlush outh
+ src/System/Process/Concurrent.hs view
@@ -0,0 +1,34 @@+-- | +-- Copyright: 2015 Joey Hess <id@joeyh.name>+-- License: BSD-2-clause+-- +-- The functions exported by this module are intended to be drop-in+-- replacements for those from System.Process, when converting a whole+-- program to use System.Console.Concurrent.++module System.Process.Concurrent where++import System.Console.Concurrent+import System.Console.Concurrent.Internal (ConcurrentProcessHandle(..))+import System.Process hiding (createProcess, waitForProcess)+import System.IO+import System.Exit++-- | Calls `createProcessConcurrent`+--+-- You should use the waitForProcess in this module on the resulting+-- ProcessHandle. Using System.Process.waitForProcess instead can have+-- mildly unexpected results.+createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)+createProcess p = do+	(i, o, e, ConcurrentProcessHandle h) <- createProcessConcurrent p+	return (i, o, e, h)++-- | Calls `waitForProcessConcurrent`+--+-- You should only use this on a ProcessHandle obtained by calling+-- createProcess from this module. Using this with a ProcessHandle+-- obtained from System.Process.createProcess etc will have extremely+-- unexpected results; it can wait a very long time before returning.+waitForProcess :: ProcessHandle -> IO ExitCode+waitForProcess = waitForProcessConcurrent . ConcurrentProcessHandle
src/Utility/Process.hs view
@@ -41,9 +41,12 @@ 	devNull, ) where -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, waitForProcess)+import qualified Utility.Process.Shim+import qualified Utility.Process.Shim as X hiding (CreateProcess(..), createProcess, runInteractiveProcess, readProcess, readProcessWithExitCode, system, rawSystem, runInteractiveCommand, runProcess)+import Utility.Process.Shim hiding (createProcess, readProcess, waitForProcess)+import Utility.Misc+import Utility.Exception+ import System.Exit import System.IO import System.Log.Logger@@ -58,9 +61,6 @@ import Data.Maybe import Prelude -import Utility.Misc-import Utility.Exception- type CreateProcessRunner = forall a. CreateProcess -> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO a) -> IO a  data StdHandle = StdinHandle | StdoutHandle | StderrHandle@@ -172,22 +172,21 @@ -- 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 = processTranscript' cmd opts Nothing+processTranscript = processTranscript' id -processTranscript' :: String -> [String] -> Maybe [(String, String)] -> (Maybe String) -> IO (String, Bool)-processTranscript' cmd opts environ input = do+processTranscript' :: (CreateProcess -> CreateProcess) -> String -> [String] -> Maybe String -> IO (String, Bool)+processTranscript' modproc cmd opts input = do #ifndef mingw32_HOST_OS {- This implementation interleves stdout and stderr in exactly the order  - the process writes them. -} 	(readf, writef) <- System.Posix.IO.createPipe 	readh <- System.Posix.IO.fdToHandle readf 	writeh <- System.Posix.IO.fdToHandle writef-	p@(_, _, _, pid) <- createProcess $+	p@(_, _, _, pid) <- createProcess $ modproc $ 		(proc cmd opts) 			{ std_in = if isJust input then CreatePipe else Inherit 			, std_out = UseHandle writeh 			, std_err = UseHandle writeh-			, env = environ 			} 	hClose writeh @@ -199,12 +198,11 @@ 	return (transcript, ok) #else {- This implementation for Windows puts stderr after stdout. -}-	p@(_, _, _, pid) <- createProcess $+	p@(_, _, _, pid) <- createProcess $ modproc $ 		(proc cmd opts) 			{ std_in = if isJust input then CreatePipe else Inherit 			, std_out = CreatePipe 			, std_err = CreatePipe-			, env = environ 			}  	getout <- mkreader (stdoutHandle p)@@ -374,7 +372,7 @@ createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) createProcess p = do 	debugProcess p-	System.Process.createProcess p+	Utility.Process.Shim.createProcess p  -- | Debugging trace for a CreateProcess. debugProcess :: CreateProcess -> IO ()@@ -394,6 +392,6 @@ -- | Wrapper around 'System.Process.waitForProcess' that does debug logging. waitForProcess ::  ProcessHandle -> IO ExitCode waitForProcess h = do-	r <- System.Process.waitForProcess h+	r <- Utility.Process.Shim.waitForProcess h 	debugM "Utility.Process" ("process done " ++ show r) 	return r
+ src/Utility/Process/Shim.hs view
@@ -0,0 +1,4 @@+module Utility.Process.Shim (module X, createProcess, waitForProcess) where++import System.Process as X hiding (createProcess, waitForProcess)+import System.Process.Concurrent
src/wrapper.hs view
@@ -50,7 +50,7 @@ netrepo = "https://github.com/joeyh/propellor.git"  main :: IO ()-main = do+main = withConcurrentOutput $ do 	args <- getArgs 	home <- myHomeDir 	let propellordir = home </> ".propellor"