packages feed

propellor 0.5.3 → 0.6.0

raw patch · 23 files changed

+500/−218 lines, 23 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Propellor.Attr: addNamedConf :: NamedConf -> SetAttr
- Propellor.Attr: fromHost :: [Host] -> HostName -> Propellor a -> Propellor (Maybe a)
- Propellor.Attr: getHostName :: Propellor HostName
- Propellor.Attr: getNamedConf :: Propellor (Map Domain NamedConf)
- Propellor.Attr: hostAttr :: Host -> Attr
- Propellor.Attr: hostAttrMap :: [Host] -> Map HostName Attr
- Propellor.Attr: hostProperties :: Host -> [Property]
- Propellor.Attr: hostname :: HostName -> Property
- Propellor.Attr: hostnameless :: Attr
- Propellor.Property: combineSetAttr :: (IsProp p, IsProp q) => p -> q -> SetAttr
- Propellor.Property: combineSetAttrs :: IsProp p => [p] -> SetAttr
- Propellor.Property.Docker: AllContainers :: ContainerFilter
- Propellor.Property.Docker: Container :: Image -> [RunParam] -> Container
- Propellor.Property.Docker: ContainerId :: HostName -> ContainerName -> ContainerId
- Propellor.Property.Docker: ContainerIdent :: Image -> HostName -> ContainerName -> [RunParam] -> ContainerIdent
- Propellor.Property.Docker: RunningContainers :: ContainerFilter
- Propellor.Property.Docker: checkProvisionedFlag :: ContainerId -> IO Bool
- Propellor.Property.Docker: clearProvisionedFlag :: ContainerId -> IO ()
- Propellor.Property.Docker: cn2hn :: ContainerName -> HostName
- Propellor.Property.Docker: commitContainer :: ContainerId -> IO (Maybe Image)
- Propellor.Property.Docker: containerDesc :: ContainerId -> Property -> Property
- Propellor.Property.Docker: containerHostName :: ContainerId -> HostName
- Propellor.Property.Docker: data Container
- Propellor.Property.Docker: data ContainerFilter
- Propellor.Property.Docker: data ContainerId
- Propellor.Property.Docker: data ContainerIdent
- Propellor.Property.Docker: dockercmd :: String
- Propellor.Property.Docker: findContainer :: [Host] -> ContainerId -> ContainerName -> (Container -> Property) -> Property
- Propellor.Property.Docker: fromContainerId :: ContainerId -> String
- Propellor.Property.Docker: genProp :: String -> (HostName -> RunParam) -> Property
- Propellor.Property.Docker: ident2id :: ContainerIdent -> ContainerId
- Propellor.Property.Docker: identFile :: ContainerId -> FilePath
- Propellor.Property.Docker: listContainers :: ContainerFilter -> IO [ContainerId]
- Propellor.Property.Docker: listImages :: IO [Image]
- Propellor.Property.Docker: mkContainer :: ContainerId -> Host -> Maybe Container
- Propellor.Property.Docker: myContainerSuffix :: String
- Propellor.Property.Docker: namedPipe :: ContainerId -> FilePath
- Propellor.Property.Docker: propellorIdent :: FilePath
- Propellor.Property.Docker: provisionContainer :: ContainerId -> Property
- Propellor.Property.Docker: provisionedFlag :: ContainerId -> FilePath
- Propellor.Property.Docker: readIdentFile :: ContainerId -> IO ContainerIdent
- Propellor.Property.Docker: removeContainer :: ContainerId -> IO Bool
- Propellor.Property.Docker: removeImage :: Image -> IO Bool
- Propellor.Property.Docker: report :: [Bool] -> Result
- Propellor.Property.Docker: runContainer :: Image -> [RunParam] -> [String] -> IO Bool
- Propellor.Property.Docker: runProp :: String -> RunParam -> Property
- Propellor.Property.Docker: runningContainer :: ContainerId -> Image -> [RunParam] -> Property
- Propellor.Property.Docker: setProvisionedFlag :: ContainerId -> IO ()
- Propellor.Property.Docker: shimdir :: ContainerId -> FilePath
- Propellor.Property.Docker: stopContainer :: ContainerId -> IO Bool
- Propellor.Property.Docker: stoppedContainer :: ContainerId -> Property
- Propellor.Property.Docker: toContainerId :: String -> Maybe ContainerId
- Propellor.Property.Docker: type RunParam = String
- Propellor.Types: instance MonadReader Attr Propellor
- Propellor.Types: runWithAttr :: Propellor p -> ReaderT Attr IO p
- Propellor.Types: setAttr :: IsProp p => p -> SetAttr
- Propellor.Types: type SetAttr = Attr -> Attr
+ Propellor.Attr: askAttr :: (Attr -> Val a) -> Propellor (Maybe a)
+ Propellor.Engine: fromHost :: [Host] -> HostName -> Propellor a -> Propellor (Maybe a)
+ Propellor.Message: actionMessage' :: (MonadIO m, ActionResult r) => Maybe HostName -> Desc -> m r -> m r
+ Propellor.Message: actionMessageOn :: (MonadIO m, ActionResult r) => HostName -> Desc -> m r -> m r
+ Propellor.PrivData: decryptPrivData :: HostName -> IO (Map PrivDataField String)
+ Propellor.PrivData: dumpPrivData :: HostName -> PrivDataField -> IO ()
+ Propellor.Property: combineAttr :: (IsProp p, IsProp q) => p -> q -> Attr
+ Propellor.Property: combineAttrs :: IsProp p => [p] -> Attr
+ Propellor.Property.Apt: cacheCleaned :: Property
+ Propellor.Property.Apt: stdSourcesListFor :: DebianSuite -> Property
+ Propellor.Property.Docker: cpuShares :: Int -> Property
+ Propellor.Property.Docker: memoryLimited :: Property
+ Propellor.Property.Obnam: backup' :: FilePath -> CronTimes -> [ObnamParam] -> NumClients -> Property
+ Propellor.Property.SiteSpecific.JoeySites: githubBackup :: Property
+ Propellor.Property.SiteSpecific.JoeySites: kiteShellBox :: Property
+ Propellor.Types: Dump :: HostName -> PrivDataField -> CmdLine
+ Propellor.Types: getAttr :: IsProp p => p -> Attr
+ Propellor.Types: hostAttr :: Host -> Attr
+ Propellor.Types: hostName :: Host -> HostName
+ Propellor.Types: hostProperties :: Host -> [Property]
+ Propellor.Types: instance MonadReader Host Propellor
+ Propellor.Types: instance Show Host
+ Propellor.Types: instance Show Property
+ Propellor.Types: runWithHost :: Propellor p -> ReaderT Host IO p
+ Propellor.Types.Dns: NamedConfMap :: (Map Domain NamedConf) -> NamedConfMap
+ Propellor.Types.Dns: fromNamedConfMap :: NamedConfMap -> Map Domain NamedConf
+ Propellor.Types.Dns: instance Eq NamedConfMap
+ Propellor.Types.Dns: instance Monoid NamedConfMap
+ Propellor.Types.Dns: instance Ord NamedConfMap
+ Propellor.Types.Dns: instance Show NamedConfMap
+ Propellor.Types.Dns: newtype NamedConfMap
- Propellor.Attr: addDNS :: Record -> SetAttr
+ Propellor.Attr: addDNS :: Record -> Property
- Propellor.Attr: pureAttrProperty :: Desc -> SetAttr -> Property
+ Propellor.Attr: pureAttrProperty :: Desc -> Attr -> Property
- Propellor.Engine: mainProperties :: Attr -> [Property] -> IO ()
+ Propellor.Engine: mainProperties :: Host -> IO ()
- Propellor.Engine: runPropellor :: Attr -> Propellor a -> IO a
+ Propellor.Engine: runPropellor :: Host -> Propellor a -> IO a
- Propellor.Property.Apt: stdSourcesList :: DebianSuite -> Property
+ Propellor.Property.Apt: stdSourcesList :: Property
- Propellor.Types: Host :: [Property] -> SetAttr -> Host
+ Propellor.Types: Host :: HostName -> [Property] -> Attr -> Host
- Propellor.Types: Propellor :: ReaderT Attr IO p -> Propellor p
+ Propellor.Types: Propellor :: ReaderT Host IO p -> Propellor p
- Propellor.Types: Property :: Desc -> Propellor Result -> SetAttr -> Property
+ Propellor.Types: Property :: Desc -> Propellor Result -> Attr -> Property
- Propellor.Types: propertyAttr :: Property -> SetAttr
+ Propellor.Types: propertyAttr :: Property -> Attr

Files

CHANGELOG view
@@ -1,3 +1,17 @@+propellor (0.6.0) unstable; urgency=medium++  * Docker containers now propagate DNS attributes out to the host they're+    docked in. So if a docker container sets a DNS alias, every container+    it's docked in will automatically be added to a DNS round-robin,+    when propellor is used to manage DNS for the domain.+  * Apt.stdSourcesList no longer needs a suite to be specified.+  * Added --dump to dump out a field of a host's privdata. Useful for editing+    it.+  * Propellor's output now includes the hostname being provisioned, or+    when provisioning a docker container, the container name.++ -- Joey Hess <joeyh@debian.org>  Thu, 05 Jun 2014 17:32:14 -0400+ propellor (0.5.3) unstable; urgency=medium    * Fix unattended-upgrades config for !stable.
config-joey.hs view
@@ -37,11 +37,12 @@ 	-- My laptop 	[ host "darkstar.kitenet.net" 		& ipv6 "2001:4830:1600:187::2" -- sixxs tunnel-		& Docker.configured+ 		& Apt.buildDep ["git-annex"] `period` Daily+		& Docker.configured 		& Docker.docked hosts "android-git-annex" -	-- Nothing super-important lives here.+	-- Nothing super-important lives here and mostly it's docker containers. 	, standardSystem "clam.kitenet.net" Unstable "amd64" 		& ipv4 "162.248.143.249" 		& ipv6 "2002:5044:5531::1"@@ -53,14 +54,9 @@ 		& Postfix.satellite 		& Docker.configured -		& alias "shell.olduse.net"-		& JoeySites.oldUseNetShellBox--		& alias "openid.kitenet.net"+		& Docker.docked hosts "oldusenet-shellbox" 		& Docker.docked hosts "openid-provider" 		 	`requires` Apt.serviceInstalledRunning "ntp"--		& alias "ancient.kitenet.net" 		& Docker.docked hosts "ancient-kitenet"  		-- I'd rather this were on diatom, but it needs unstable.@@ -76,9 +72,15 @@ 		& alias "znc.kitenet.net" 		& JoeySites.ircBouncer -		-- Nothing is using https on clam, so listen on that port-		-- for ssh, for traveling on bad networks.-		& "/etc/ssh/sshd_config" `File.containsLine` "Port 443"+		-- For https port 443, shellinabox with ssh login to+		-- kitenet.net+		& alias "shell.kitenet.net"+		& JoeySites.kiteShellBox++		-- Nothing is using http port 80 on clam, so listen on+		-- that port for ssh, for traveling on bad networks that+		-- block 22.+		& "/etc/ssh/sshd_config" `File.containsLine` "Port 80" 			`onChange` Service.restarted "ssh"  		& Docker.garbageCollected `period` Daily@@ -161,7 +163,25 @@ 		 		& Dns.secondaryFor ["animx"] hosts "animx.eu.org" +	-- storage and backup server+	, standardSystem "elephant.kitenet.net" Unstable "amd64"+		& ipv4 "193.234.225.114" +		& Hostname.sane+		& Postfix.satellite+		& Apt.unattendedUpgrades++		& alias "eubackup.kitenet.net"+		& Apt.installed ["obnam", "sshfs", "rsync"]+		& JoeySites.githubBackup++		& alias "podcatcher.kitenet.net"+		& Apt.installed ["git-annex"]+		+		& Docker.configured+		! Docker.docked hosts "voltagex"+		& Docker.garbageCollected `period` (Weekly (Just 1))+ 	    --'                        __|II|      ,. 	  ----                      __|II|II|__   (  \_,/\ 	 ------'\o/-'-.-'-.-'-.- __|II|II|II|II|___/   __/ -'-.-'-.-'-.-'-.-'-@@ -179,17 +199,24 @@ 	-- My own openid provider. Uses php, so containerized for security 	-- and administrative sanity. 	, standardContainer "openid-provider" Stable "amd64"+		& alias "openid.kitenet.net" 		& Docker.publish "8081:80" 		& OpenId.providerFor ["joey", "liw"] 			"openid.kitenet.net:8081"  	-- Exhibit: kite's 90's website. 	, standardContainer "ancient-kitenet" Stable "amd64"+		& alias "ancient.kitenet.net" 		& Docker.publish "1994:80" 		& Apt.serviceInstalledRunning "apache2" 		& Git.cloned "root" "git://kitenet-net.branchable.com/" "/var/www" 			(Just "remotes/origin/old-kitenet.net") 	+	, standardContainer "oldusenet-shellbox" Stable "amd64"+		& alias "shell.olduse.net"+		& Docker.publish "4200:4200"+		& JoeySites.oldUseNetShellBox+ 	-- git-annex autobuilder containers 	, GitAnnexBuilder.standardAutoBuilderContainer dockerImage "amd64" 15 "2h" 	, GitAnnexBuilder.standardAutoBuilderContainer dockerImage "i386" 45 "2h"@@ -202,14 +229,25 @@ 	, let gitannexdir = GitAnnexBuilder.homedir </> "git-annex" 	  in GitAnnexBuilder.androidContainer dockerImage "android-git-annex" doNothing gitannexdir 		& Docker.volume ("/home/joey/src/git-annex:" ++ gitannexdir)++	-- temp for an acquantance+	, standardContainer "voltagex" Stable "amd64"+		& Docker.publish "22022:22"+		& Docker.memory "500m"+		& Docker.cpuShares 1+		& Apt.serviceInstalledRunning "ssh"+		& Ssh.permitRootLogin True+		& Ssh.passwordAuthentication True+		& User.hasSomePassword "root"+ 	] ++ monsters  -- This is my standard system setup. standardSystem :: HostName -> DebianSuite -> Architecture -> Host standardSystem hn suite arch = host hn 	& os (System (Debian suite) arch)-	& Apt.stdSourcesList suite-		`onChange` Apt.upgrade+	& Apt.stdSourcesList `onChange` Apt.upgrade+	& Apt.cacheCleaned 	& Apt.installed ["etckeeper"] 	& Apt.installed ["ssh"] 	& GitHome.installedFor "root"@@ -231,9 +269,11 @@ -- This is my standard container setup, featuring automatic upgrades. standardContainer :: Docker.ContainerName -> DebianSuite -> Architecture -> Host standardContainer name suite arch = Docker.container name (dockerImage system)-	& os (System (Debian suite) arch)-	& Apt.stdSourcesList suite+	& os system+	& Apt.stdSourcesList `onChange` Apt.upgrade+	& Apt.installed ["systemd"] 	& Apt.unattendedUpgrades+	& Apt.cacheCleaned   where 	system = System (Debian suite) arch @@ -306,8 +346,33 @@ 		& alias "wortroot.kitenet.net" 		& alias "www.wortroot.kitenet.net" 		& alias "joey.kitenet.net"-		& alias "ipv6.kitenet.net"+		& alias "anna.kitenet.net" 		& alias "bitlbee.kitenet.net"+		{- Remaining services on kite:+		 - +		 - mail+		 -   postfix+		 -   postgrey+		 -   mailman+		 -   spamassassin+		 -   sqwebmail+		 -   courier+		 -     imap+		 -     tls+		 - apache+		 -   some static websites+		 - bitlbee+		 - prosody+		 -   (used by anna and daddy's git-annex)+		 - named+		 -   (branchable is still pushing to here+		 -    (thinking it's ns2.branchable.com), but it's no+		 -   longer a primary or secondary for anything)+		 - ftpd (EOL)+		 -+		 - user shell stuff:+		 -   pine, zsh, make, git-annex, myrepos, ...+		 -} 	, host "mouse.kitenet.net" 		& ipv6 "2001:4830:1600:492::2" 	, host "beaver.kitenet.net"
config-simple.hs view
@@ -24,7 +24,8 @@ hosts :: [Host] hosts = 	[ host "mybox.example.com"-		& Apt.stdSourcesList Unstable+		& os (System (Debian Unstable) "amd64")+		& Apt.stdSourcesList 		& Apt.unattendedUpgrades 		& Apt.installed ["etckeeper"] 		& Apt.installed ["ssh"]@@ -36,7 +37,9 @@ 		& Cron.runPropellor "30 * * * *"  	-- A generic webserver in a Docker container.-	, Docker.container "webserver" "joeyh/debian-unstable"+	, Docker.container "webserver" "joeyh/debian-stable"+		& os (System (Debian Stable) "amd64")+		& Apt.stdSourcesList 		& Docker.publish "80:80" 		& Docker.volume "/var/www:/var/www" 		& Apt.serviceInstalledRunning "apache2"
debian/changelog view
@@ -1,3 +1,17 @@+propellor (0.6.0) unstable; urgency=medium++  * Docker containers now propagate DNS attributes out to the host they're+    docked in. So if a docker container sets a DNS alias, every container+    it's docked in will automatically be added to a DNS round-robin,+    when propellor is used to manage DNS for the domain.+  * Apt.stdSourcesList no longer needs a suite to be specified.+  * Added --dump to dump out a field of a host's privdata. Useful for editing+    it.+  * Propellor's output now includes the hostname being provisioned, or+    when provisioning a docker container, the container name.++ -- Joey Hess <joeyh@debian.org>  Thu, 05 Jun 2014 17:32:14 -0400+ propellor (0.5.3) unstable; urgency=medium    * Fix unattended-upgrades config for !stable.
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 0.5.3+Version: 0.6.0 Cabal-Version: >= 1.6 License: BSD3 Maintainer: Joey Hess <joey@kitenet.net>@@ -35,7 +35,7 @@  Executable wrapper   Main-Is: wrapper.hs-  GHC-Options: -Wall -threaded+  GHC-Options: -Wall -threaded -O0   Hs-Source-Dirs:  src   Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5,     IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,@@ -47,7 +47,7 @@  Executable config   Main-Is: config.hs-  GHC-Options: -Wall -threaded+  GHC-Options: -Wall -threaded -O0   Hs-Source-Dirs:  src   Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5,     IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,@@ -58,7 +58,7 @@     Build-Depends: unix  Library-  GHC-Options: -Wall+  GHC-Options: -Wall -O0   Hs-Source-Dirs:  src   Build-Depends: MissingH, directory, filepath, base >= 4.5, base < 5,     IfElse, process, bytestring, hslogger, unix-compat, ansi-terminal,
src/Propellor/Attr.hs view
@@ -9,86 +9,67 @@ import qualified Data.Set as S import qualified Data.Map as M import Data.Maybe+import Data.Monoid import Control.Applicative -pureAttrProperty :: Desc -> SetAttr -> Property +pureAttrProperty :: Desc -> Attr -> Property  pureAttrProperty desc = Property ("has " ++ desc) (return NoChange) -hostname :: HostName -> Property-hostname name = pureAttrProperty ("hostname " ++ name) $-	\d -> d { _hostname = name }--getHostName :: Propellor HostName-getHostName = asks _hostname+askAttr :: (Attr -> Val a) -> Propellor (Maybe a)+askAttr f = asks (fromVal . f . hostAttr)  os :: System -> Property os system = pureAttrProperty ("Operating " ++ show system) $-	\d -> d { _os = Just system }+	mempty { _os = Val system }  getOS :: Propellor (Maybe System)-getOS = asks _os+getOS = askAttr _os  -- | Indidate that a host has an A record in the DNS. -- -- TODO check at run time if the host really has this address. -- (Can't change the host's address, but as a sanity check.) ipv4 :: String -> Property-ipv4 addr = pureAttrProperty ("ipv4 " ++ addr)-	(addDNS $ Address $ IPv4 addr)+ipv4 = addDNS . Address . IPv4  -- | Indidate that a host has an AAAA record in the DNS. ipv6 :: String -> Property-ipv6 addr = pureAttrProperty ("ipv6 " ++ addr)-	(addDNS $ Address $ IPv6 addr)+ipv6 = addDNS . Address . IPv6  -- | Indicates another name for the host in the DNS.+--+-- When the host's ipv4/ipv6 addresses are known, the alias is set up+-- to use their address, rather than using a CNAME. This avoids various+-- problems with CNAMEs, and also means that when multiple hosts have the+-- same alias, a DNS round-robin is automatically set up. alias :: Domain -> Property-alias domain = pureAttrProperty ("alias " ++ domain)-	(addDNS $ CNAME $ AbsDomain domain)--addDNS :: Record -> SetAttr-addDNS record d = d { _dns = S.insert record (_dns d) }+alias = addDNS . CNAME . AbsDomain --- | Adds a DNS NamedConf stanza.------ Note that adding a Master stanza for a domain always overrides an--- existing Secondary stanza, while a Secondary stanza is only added--- when there is no existing Master stanza.-addNamedConf :: NamedConf -> SetAttr-addNamedConf conf d = d { _namedconf = new }+addDNS :: Record -> Property+addDNS r = pureAttrProperty (rdesc r) $+	mempty { _dns = S.singleton r }   where-	m = _namedconf d-	domain = confDomain conf-	new = case (confDnsServerType conf, confDnsServerType <$> M.lookup domain m) of-		(Secondary, Just Master) -> m-		_  -> M.insert domain conf m+	rdesc (CNAME d) = unwords ["alias", ddesc d]+	rdesc (Address (IPv4 addr)) = unwords ["ipv4", addr]+	rdesc (Address (IPv6 addr)) = unwords ["ipv6", addr]+	rdesc (MX n d) = unwords ["MX", show n, ddesc d]+	rdesc (NS d) = unwords ["NS", ddesc d]+	rdesc (TXT s) = unwords ["TXT", s]+	rdesc (SRV x y z d) = unwords ["SRV", show x, show y, show z, ddesc d] -getNamedConf :: Propellor (M.Map Domain NamedConf)-getNamedConf = asks _namedconf+	ddesc (AbsDomain domain) = domain+	ddesc (RelDomain domain) = domain+	ddesc RootDomain = "@"  sshPubKey :: String -> Property sshPubKey k = pureAttrProperty ("ssh pubkey known") $-	\d -> d { _sshPubKey = Just k }+	mempty { _sshPubKey = Val k }  getSshPubKey :: Propellor (Maybe String)-getSshPubKey = asks _sshPubKey--hostnameless :: Attr-hostnameless = newAttr (error "hostname Attr not specified")--hostAttr :: Host -> Attr-hostAttr (Host _ mkattrs) = mkattrs hostnameless--hostProperties :: Host -> [Property]-hostProperties (Host ps _) = ps+getSshPubKey = askAttr _sshPubKey  hostMap :: [Host] -> M.Map HostName Host-hostMap l = M.fromList $ zip (map (_hostname . hostAttr) l) l --hostAttrMap :: [Host] -> M.Map HostName Attr-hostAttrMap l = M.fromList $ zip (map _hostname attrs) attrs-  where-	attrs = map hostAttr l+hostMap l = M.fromList $ zip (map hostName l) l   findHost :: [Host] -> HostName -> Maybe Host findHost l hn = M.lookup hn (hostMap l)@@ -100,12 +81,3 @@ hostAddresses hn hosts = case hostAttr <$> findHost hosts hn of 	Nothing -> [] 	Just attr -> mapMaybe getIPAddr $ S.toList $ _dns attr---- | Lifts an action into a different host.------ For example, `fromHost hosts "otherhost" getSshPubKey`-fromHost :: [Host] -> HostName -> Propellor a -> Propellor (Maybe a)-fromHost l hn getter = case findHost l hn of-	Nothing -> return Nothing-	Just h -> liftIO $ Just <$>-		runReaderT (runWithAttr getter) (hostAttr h)
src/Propellor/CmdLine.hs view
@@ -27,6 +27,7 @@ 		, "  propellor hostname" 		, "  propellor --spin hostname" 		, "  propellor --set hostname field"+		, "  propellor --dump hostname field" 		, "  propellor --add-key keyid" 		] 	exitFailure@@ -38,9 +39,8 @@   	go ("--spin":h:[]) = return $ Spin h   	go ("--boot":h:[]) = return $ Boot h 	go ("--add-key":k:[]) = return $ AddKey k-	go ("--set":h:f:[]) = case readish f of-		Just pf -> return $ Set h pf-		Nothing -> errorMessage $ "Unknown privdata field " ++ f+	go ("--set":h:f:[]) = withprivfield f (return . Set h)+	go ("--dump":h:f:[]) = withprivfield f (return . Dump h) 	go ("--continue":s:[]) = case readish s of 		Just cmdline -> return $ Continue cmdline 		Nothing -> errorMessage "--continue serialization failure"@@ -56,6 +56,10 @@ 			else return $ Run s 	go _ = usage +	withprivfield s f = case readish s of+		Just pf -> f pf+		Nothing -> errorMessage $ "Unknown privdata field " ++ s+ defaultMain :: [Host] -> IO () defaultMain hostlist = do 	DockerShim.cleanEnv@@ -66,25 +70,23 @@   where 	go _ (Continue cmdline) = go False cmdline 	go _ (Set hn field) = setPrivData hn field+	go _ (Dump hn field) = dumpPrivData hn field 	go _ (AddKey keyid) = addKey keyid-	go _ (Chain hn) = withprops hn $ \attr ps -> do-		r <- runPropellor attr $ ensureProperties ps+	go _ (Chain hn) = withhost hn $ \h -> do+		r <- runPropellor h $ ensureProperties $ hostProperties h 		putStrLn $ "\n" ++ show r 	go _ (Docker hn) = Docker.chain hn 	go True cmdline@(Spin _) = buildFirst cmdline $ go False cmdline 	go True cmdline = updateFirst cmdline $ go False cmdline-	go False (Spin hn) = withprops hn $ const . const $ spin hn+	go False (Spin hn) = withhost hn $ const $ spin hn 	go False (Run hn) = ifM ((==) 0 <$> getRealUserID)-		( onlyProcess $ withprops hn mainProperties+		( onlyProcess $ withhost hn mainProperties 		, go True (Spin hn) 		)-	go False (Boot hn) = onlyProcess $ withprops hn boot+	go False (Boot hn) = onlyProcess $ withhost hn boot -	withprops :: HostName -> (Attr -> [Property] -> IO ()) -> IO ()-	withprops hn a = maybe-		(unknownhost hn)-		(\h -> a (hostAttr h) (hostProperties h))-		(findHost hostlist hn)+	withhost :: HostName -> (Host -> IO ()) -> IO ()+	withhost hn a = maybe (unknownhost hn) a (findHost hostlist hn)  onlyProcess :: IO a -> IO a onlyProcess a = bracket lock unlock (const a)@@ -279,15 +281,15 @@ 	len = length marker 	matches = filter (marker `isPrefixOf`) $ lines s -boot :: Attr -> [Property] -> IO ()-boot attr ps = do+boot :: Host -> IO ()+boot h = do 	sendMarked stdout statusMarker $ show Ready 	reply <- hGetContentsStrict stdin  	makePrivDataDir 	maybe noop (writeFileProtected privDataLocal) $ 		fromMarked privDataMarker reply-	mainProperties attr ps+	mainProperties h  addKey :: String -> IO () addKey keyid = exitBool =<< allM id [ gpg, gitadd, gitconfig, gitcommit ]
src/Propellor/Engine.hs view
@@ -5,20 +5,22 @@ import System.Exit import System.IO import Data.Monoid+import Control.Applicative import System.Console.ANSI import "mtl" Control.Monad.Reader  import Propellor.Types import Propellor.Message import Propellor.Exception+import Propellor.Attr -runPropellor :: Attr -> Propellor a -> IO a-runPropellor attr a = runReaderT (runWithAttr a) attr+runPropellor :: Host -> Propellor a -> IO a+runPropellor host a = runReaderT (runWithHost a) host -mainProperties :: Attr -> [Property] -> IO ()-mainProperties attr ps = do-	r <- runPropellor attr $-		ensureProperties [Property "overall" (ensureProperties ps) id]+mainProperties :: Host -> IO ()+mainProperties host = do+	r <- runPropellor host $+		ensureProperties [Property "overall" (ensureProperties $ hostProperties host) mempty] 	setTitle "propellor: done" 	hFlush stdout 	case r of@@ -30,8 +32,18 @@   where 	ensure [] rs = return rs 	ensure (l:ls) rs = do-		r <- actionMessage (propertyDesc l) (ensureProperty l)+		hn <- asks hostName+		r <- actionMessageOn hn (propertyDesc l) (ensureProperty l) 		ensure ls (r <> rs)  ensureProperty :: Property -> Propellor Result ensureProperty = catchPropellor . propertySatisfy++-- | Lifts an action into a different host.+--+-- For example, `fromHost hosts "otherhost" getSshPubKey`+fromHost :: [Host] -> HostName -> Propellor a -> Propellor (Maybe a)+fromHost l hn getter = case findHost l hn of+	Nothing -> return Nothing+	Just h -> liftIO $ Just <$>+		runReaderT (runWithHost getter) h
src/Propellor/Message.hs view
@@ -12,7 +12,15 @@ -- | Shows a message while performing an action, with a colored status -- display. actionMessage :: (MonadIO m, ActionResult r) => Desc -> m r -> m r-actionMessage desc a = do+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 = actionMessage' . Just++actionMessage' :: (MonadIO m, ActionResult r) => Maybe HostName -> Desc -> m r -> m r+actionMessage' mhn desc a = do 	liftIO $ do 		setTitle $ "propellor: " ++ desc 		hFlush stdout@@ -21,12 +29,19 @@  	liftIO $ do 		setTitle "propellor: running"-		let (msg, intensity, color) = getActionResult r+		showhn mhn 		putStr $ desc ++ " ... "+		let (msg, intensity, color) = getActionResult r 		colorLine intensity color msg 		hFlush stdout  	return r+  where+	showhn Nothing = return ()+	showhn (Just hn) = do+		setSGR [SetColor Foreground Dull Cyan]+		putStr (hn ++ " ")+		setSGR []  warningMessage :: MonadIO m => String -> m () warningMessage s = liftIO $ colorLine Vivid Magenta $ "** warning: " ++ s
src/Propellor/PrivData.hs view
@@ -13,7 +13,6 @@ import "mtl" Control.Monad.Reader  import Propellor.Types-import Propellor.Attr import Propellor.Message import Utility.Monad import Utility.PartialPrelude@@ -30,7 +29,7 @@ withPrivData field a = maybe missing a =<< liftIO (getPrivData field)   where 	missing = do-		host <- getHostName+		host <- asks hostName 		let host' = if ".docker" `isSuffixOf` host 			then "$parent_host" 			else host@@ -50,7 +49,7 @@ 	value <- chomp <$> hGetContentsStrict stdin 	makePrivDataDir 	let f = privDataFile host-	m <- fromMaybe M.empty . readish <$> gpgDecrypt f+	m <- decryptPrivData host 	let m' = M.insert field value m 	gpgEncrypt f (show m') 	putStrLn "Private data set."@@ -59,6 +58,16 @@ 	chomp s 		| end s == "\n" = chomp (beginning s) 		| otherwise = s++dumpPrivData :: HostName -> PrivDataField -> IO ()+dumpPrivData host field = go . M.lookup field =<< decryptPrivData host+  where+	go Nothing = error "Requested privdata is not set."+	go (Just s) = putStrLn s++decryptPrivData :: HostName -> IO (M.Map PrivDataField String)+decryptPrivData host = fromMaybe M.empty . readish+	<$> gpgDecrypt (privDataFile host)  makePrivDataDir :: IO () makePrivDataDir = createDirectoryIfMissing False privDataDir
src/Propellor/Property.hs view
@@ -5,12 +5,10 @@ import System.Directory import Control.Monad import Data.Monoid-import Data.List import Control.Monad.IfElse import "mtl" Control.Monad.Reader  import Propellor.Types-import Propellor.Types.Attr import Propellor.Attr import Propellor.Engine import Utility.Monad@@ -18,19 +16,19 @@  -- Constructs a Property. property :: Desc -> Propellor Result -> Property-property d s = Property d s id+property d s = Property d s mempty  -- | Combines a list of properties, resulting in a single property -- that when run will run each property in the list in turn, -- and print out the description of each as it's run. Does not stop -- on failure; does propigate overall success/failure. propertyList :: Desc -> [Property] -> Property-propertyList desc ps = Property desc (ensureProperties ps) (combineSetAttrs ps)+propertyList desc ps = Property desc (ensureProperties ps) (combineAttrs ps)  -- | Combines a list of properties, resulting in one property that -- ensures each in turn, stopping on failure. combineProperties :: Desc -> [Property] -> Property-combineProperties desc ps = Property desc (go ps NoChange) (combineSetAttrs ps)+combineProperties desc ps = Property desc (go ps NoChange) (combineAttrs ps)   where   	go [] rs = return rs 	go (l:ls) rs = do@@ -69,7 +67,7 @@ --- | Whenever a change has to be made for a Property, causes a hook -- Property to also be run, but not otherwise. onChange :: Property -> Property -> Property-p `onChange` hook = Property (propertyDesc p) satisfy (combineSetAttr p hook)+p `onChange` hook = Property (propertyDesc p) satisfy (combineAttr p hook)   where 	satisfy = do 		r <- ensureProperty p@@ -130,21 +128,19 @@ -- > 	! oldproperty -- > 	& otherproperty host :: HostName -> Host-host hn = Host [] (\_ -> newAttr hn)+host hn = Host hn [] mempty  -- | Adds a property to a Host -- -- Can add Properties and RevertableProperties (&) :: IsProp p => Host -> p -> Host-(Host ps as) & p = Host (ps ++ [toProp p]) (setAttr p . as)+(Host hn ps as) & p = Host hn (ps ++ [toProp p]) (as <> getAttr p)  infixl 1 &  -- | Adds a property to the Host in reverted form. (!) :: Host -> RevertableProperty -> Host-(Host ps as) ! p = Host (ps ++ [toProp q]) (setAttr q . as)-  where-	q = revert p+h ! p = h & revert p  infixl 1 ! @@ -152,12 +148,12 @@ adjustProperty :: Property -> (Propellor Result -> Propellor Result) -> Property adjustProperty p f = p { propertySatisfy = f (propertySatisfy p) } --- Combines the Attr settings of two properties.-combineSetAttr :: (IsProp p, IsProp q) => p -> q -> SetAttr-combineSetAttr p q = setAttr p . setAttr q+-- Combines the Attr of two properties.+combineAttr :: (IsProp p, IsProp q) => p -> q -> Attr+combineAttr p q = getAttr p <> getAttr q -combineSetAttrs :: IsProp p => [p] -> SetAttr-combineSetAttrs = foldl' (.) id . map setAttr+combineAttrs :: IsProp p => [p] -> Attr+combineAttrs = mconcat . map getAttr  makeChange :: IO () -> Propellor Result makeChange a = liftIO a >> return MadeChange
src/Propellor/Property/Apt.hs view
@@ -64,13 +64,20 @@ 	| otherwise = []  -- | Makes sources.list have a standard content using the mirror CDN,--- with a particular DebianSuite.+-- with the Debian suite configured by the os. -- -- Since the CDN is sometimes unreliable, also adds backup lines using -- kernel.org.-stdSourcesList :: DebianSuite -> Property-stdSourcesList suite = stdSourcesList' suite []+stdSourcesList :: Property+stdSourcesList = withOS ("standard sources.list") $ \o -> +	case o of+		(Just (System (Debian suite) _)) -> +			ensureProperty $ stdSourcesListFor suite+		_ -> error "os is not declared to be Debian" +stdSourcesListFor :: DebianSuite -> Property+stdSourcesListFor suite = stdSourcesList' suite []+ -- | Adds additional sources.list generators. -- -- Note that if a Property needs to enable an apt source, it's better@@ -254,3 +261,9 @@ 				hPutStr h (pubkey k) 				hClose h 		nukeFile $ f ++ "~" -- gpg dropping++-- | Cleans apt's cache of downloaded packages to avoid using up disk+-- space.+cacheCleaned :: Property+cacheCleaned = cmdProperty "apt-get" ["clean"]+	`describe` "apt cache cleaned" 
src/Propellor/Property/Dns.hs view
@@ -129,9 +129,9 @@  otherServers :: DnsServerType -> [Host] -> Domain -> [HostName] otherServers wantedtype hosts domain =-	M.keys $ M.filter wanted $ hostAttrMap hosts+	M.keys $ M.filter wanted $ hostMap hosts   where-	wanted attr = case M.lookup domain (_namedconf attr) of+	wanted h = case M.lookup domain (fromNamedConfMap $ _namedconf $ hostAttr h) of 		Nothing -> False 		Just conf -> confDnsServerType conf == wantedtype 			&& confDomain conf == domain@@ -339,9 +339,9 @@ 		, map hostrecords inzdomain 		, map addcnames (M.elems m) 		]-	in (Zone zdomain soa (nub zhosts), warnings)+	in (Zone zdomain soa (simplify zhosts), warnings)   where-	m = hostAttrMap hosts+	m = hostMap hosts 	-- Known hosts with hostname located in the zone's domain. 	inzdomain = M.elems $ M.filterWithKey (\hn _ -> inDomain zdomain $ AbsDomain $ hn) m 	@@ -350,12 +350,13 @@ 	-- 	-- If a host lacks any IPAddr, it's probably a misconfiguration, 	-- so warn.-	hostips :: Attr -> [Either WarningMessage (BindDomain, Record)]-	hostips attr-		| null l = [Left $ "no IP address defined for host " ++ _hostname attr]+	hostips :: Host -> [Either WarningMessage (BindDomain, Record)]+	hostips h+		| null l = [Left $ "no IP address defined for host " ++ hostName h] 		| otherwise = map Right l 	  where-		l = zip (repeat $ AbsDomain $ _hostname attr)+		attr = hostAttr h+		l = zip (repeat $ AbsDomain $ hostName h) 			(map Address $ getAddresses attr)  	-- Any host, whether its hostname is in the zdomain or not,@@ -370,10 +371,11 @@ 	-- 	-- We typically know the host's IPAddrs anyway. 	-- So we can just use the IPAddrs.-	addcnames :: Attr -> [Either WarningMessage (BindDomain, Record)]-	addcnames attr = concatMap gen $ filter (inDomain zdomain) $+	addcnames :: Host -> [Either WarningMessage (BindDomain, Record)]+	addcnames h = concatMap gen $ filter (inDomain zdomain) $ 		mapMaybe getCNAME $ S.toList (_dns attr) 	  where+		attr = hostAttr h 		gen c = case getAddresses attr of 			[] -> [ret (CNAME c)] 			l -> map (ret . Address) l@@ -381,12 +383,24 @@ 		  	ret record = Right (c, record) 	 	-- Adds any other DNS records for a host located in the zdomain.-	hostrecords :: Attr -> [Either WarningMessage (BindDomain, Record)]-	hostrecords attr = map Right l+	hostrecords :: Host -> [Either WarningMessage (BindDomain, Record)]+	hostrecords h = map Right l 	  where-		l = zip (repeat $ AbsDomain $ _hostname attr)+		attr = hostAttr h+		l = zip (repeat $ AbsDomain $ hostName h) 			(S.toList $ S.filter (\r -> isNothing (getIPAddr r) && isNothing (getCNAME r)) (_dns attr)) +	-- Simplifies the list of hosts. Remove duplicate entries.+	-- Also, filter out any CHAMES where the same domain has an+	-- IP address, since that's not legal.+	simplify :: [(BindDomain, Record)] -> [(BindDomain, Record)]+	simplify l = nub $ filter (not . dupcname ) l+	  where+		dupcname (d, CNAME _) | any (matchingaddr d) l = True+		dupcname _ = False+		matchingaddr d (d', (Address _)) | d == d' = True+		matchingaddr _ _ = False+ inDomain :: Domain -> BindDomain -> Bool inDomain domain (AbsDomain d) = domain == d || ('.':domain) `isSuffixOf` d inDomain _ _ = False -- can't tell, so assume not@@ -403,3 +417,10 @@   where 	dotbase = '.':base +addNamedConf :: NamedConf -> Attr+addNamedConf conf = mempty { _namedconf = NamedConfMap (M.singleton domain conf) }+  where+       domain = confDomain conf++getNamedConf :: Propellor (M.Map Domain NamedConf)+getNamedConf = asks $ fromNamedConfMap . _namedconf . hostAttr
src/Propellor/Property/Docker.hs view
@@ -5,7 +5,33 @@ -- The existance of a docker container is just another Property of a system, -- which propellor can set up. See config.hs for an example. -module Propellor.Property.Docker where+module Propellor.Property.Docker (+	-- * Host properties+	installed,+	configured,+	container,+	docked,+	memoryLimited,+	garbageCollected,+	Image,+	ContainerName,+	-- * Container configuration+	dns,+	hostname,+	name,+	publish,+	expose,+	user,+	volume,+	volumes_from,+	workdir,+	memory,+	cpuShares,+	link,+	ContainerAlias,+	-- * Internal use+	chain,+) where  import Propellor import Propellor.SimpleSh@@ -16,23 +42,24 @@ import Utility.SafeCommand import Utility.Path -import Control.Concurrent.Async+import Control.Concurrent.Async hiding (link) import System.Posix.Directory import System.Posix.Process import Data.List import Data.List.Utils+import qualified Data.Set as S +installed :: Property+installed = Apt.installed ["docker.io"]+ -- | Configures docker with an authentication file, so that images can be--- pushed to index.docker.io.+-- pushed to index.docker.io. Optional. configured :: Property configured = property "docker configured" go `requires` installed   where 	go = withPrivData DockerAuthentication $ \cfg -> ensureProperty $  		"/root/.dockercfg" `File.hasContent` (lines cfg) -installed :: Property-installed = Apt.installed ["docker.io"]- -- | A short descriptive name for a container. -- Should not contain whitespace or other unusual characters, -- only [a-zA-Z0-9_-] are allowed@@ -45,29 +72,39 @@ -- >    & Apt.installed {"apache2"] -- >    & ... container :: ContainerName -> Image -> Host-container cn image = Host [] (\_ -> attr)+container cn image = Host hn [] attr   where-	attr = (newAttr (cn2hn cn)) { _dockerImage = Just image }+	attr = dockerAttr $ mempty { _dockerImage = Val image }+	hn = cn2hn cn  cn2hn :: ContainerName -> HostName cn2hn cn = cn ++ ".docker" --- | Ensures that a docker container is set up and running. The container--- has its own Properties which are handled by running propellor--- inside the container.+-- | Ensures that a docker container is set up and running, finding+-- its configuration in the passed list of hosts.+-- +-- The container has its own Properties which are handled by running+-- propellor inside the container. --+-- Additionally, the container can have DNS attributes, such as a CNAME.+-- These become attributes of the host(s) it's docked in.+-- -- Reverting this property ensures that the container is stopped and -- removed. docked 	:: [Host] 	-> ContainerName 	-> RevertableProperty-docked hosts cn = RevertableProperty (go "docked" setup) (go "undocked" teardown)+docked hosts cn = RevertableProperty+	((maybe id exposeDnsAttrs mhost) (go "docked" setup))+	(go "undocked" teardown)   where 	go desc a = property (desc ++ " " ++ cn) $ do-		hn <- getHostName+		hn <- asks hostName   		let cid = ContainerId hn cn-		ensureProperties [findContainer hosts cid cn $ a cid]+		ensureProperties [findContainer mhost cid cn $ a cid]+		+	mhost = findHost hosts (cn2hn cn)  	setup cid (Container image runparams) = 		provisionContainer cid@@ -86,13 +123,17 @@ 					] 			] +exposeDnsAttrs :: Host -> Property -> Property+exposeDnsAttrs (Host _ _ containerattr) p = combineProperties (propertyDesc p) $+	p : map addDNS (S.toList $ _dns containerattr)+ findContainer-	:: [Host]+	:: Maybe Host 	-> ContainerId 	-> ContainerName 	-> (Container -> Property) 	-> Property-findContainer hosts cid cn mk = case findHost hosts (cn2hn cn) of+findContainer mhost cid cn mk = case mhost of 	Nothing -> cantfind 	Just h -> maybe cantfind mk (mkContainer cid h)   where@@ -103,10 +144,10 @@  mkContainer :: ContainerId -> Host -> Maybe Container mkContainer cid@(ContainerId hn _cn) h = Container-	<$> _dockerImage attr+	<$> fromVal (_dockerImage attr) 	<*> pure (map (\a -> a hn) (_dockerRunParams attr))   where-	attr = hostAttr h'+	attr = _dockerattr $ hostAttr h'   	h' = h 		-- expose propellor directory inside the container 		& volume (localdir++":"++localdir)@@ -131,6 +172,20 @@ 	gcimages = property "docker images garbage collected" $ do 		liftIO $ report <$> (mapM removeImage =<< listImages) +-- | Configures the kernel to respect docker memory limits. +--+-- This assumes the system boots using grub 2. And that you don't need any+-- other GRUB_CMDLINE_LINUX_DEFAULT settings.+--+-- Only takes effect after reboot. (Not automated.)+memoryLimited :: Property+memoryLimited = "/etc/default/grub" `File.containsLine` cfg+	`describe` "docker memory limited" +	`onChange` cmdProperty "update-grub" []+  where+	cmdline = "cgroup_enable=memory swapaccount=1"+	cfg = "GRUB_CMDLINE_LINUX_DEFAULT=\""++cmdline++"\""+ data Container = Container Image [RunParam]  -- | Parameters to pass to `docker run` when creating a container.@@ -181,10 +236,20 @@ workdir = runProp "workdir"  -- | Memory limit for container.---Format: <number><optional unit>, where unit = b, k, m or g+-- Format: <number><optional unit>, where unit = b, k, m or g+--+-- Note: Only takes effect when the host has the memoryLimited property+-- enabled. memory :: String -> Property memory = runProp "memory" +-- | CPU shares (relative weight).+--+-- By default, all containers run at the same priority, but you can tell+-- the kernel to give more CPU time to a container using this property.+cpuShares :: Int -> Property+cpuShares = runProp "cpu-shares" . show+ -- | Link with another container on the same host. link :: ContainerName -> ContainerAlias -> Property link linkwith calias = genProp "link" $ \hn ->@@ -205,9 +270,6 @@ data ContainerIdent = ContainerIdent Image HostName ContainerName [RunParam] 	deriving (Read, Show, Eq) -ident2id :: ContainerIdent -> ContainerId-ident2id (ContainerIdent _ hn cn _) = ContainerId hn cn- toContainerId :: String -> Maybe ContainerId toContainerId s 	| myContainerSuffix `isSuffixOf` s = case separate (== '.') (desuffix s) of@@ -407,14 +469,17 @@ listImages = lines <$> readProcess dockercmd ["images", "--all", "--quiet"]  runProp :: String -> RunParam -> Property-runProp field val = pureAttrProperty (param) $ \attr ->-	attr { _dockerRunParams = _dockerRunParams attr ++ [\_ -> "--"++param] }+runProp field val = pureAttrProperty (param) $ dockerAttr $+	mempty { _dockerRunParams = [\_ -> "--"++param] }   where 	param = field++"="++val  genProp :: String -> (HostName -> RunParam) -> Property-genProp field mkval = pureAttrProperty field $ \attr ->-	attr { _dockerRunParams = _dockerRunParams attr ++ [\hn -> "--"++field++"=" ++ mkval hn] }+genProp field mkval = pureAttrProperty field $ dockerAttr $+	mempty { _dockerRunParams = [\hn -> "--"++field++"=" ++ mkval hn] }++dockerAttr :: DockerAttr -> Attr+dockerAttr a = mempty { _dockerattr = a }  -- | The ContainerIdent of a container is written to -- /.propellor-ident inside it. This can be checked to see if
src/Propellor/Property/Hostname.hs view
@@ -9,7 +9,7 @@ -- A FQDN also configures /etc/hosts, with an entry for 127.0.1.1, which is -- standard at least on Debian to set the FDQN (127.0.0.1 is localhost). sane :: Property-sane = property ("sane hostname") (ensureProperty . setTo =<< getHostName)+sane = property ("sane hostname") (ensureProperty . setTo =<< asks hostName)  setTo :: HostName -> Property setTo hn = combineProperties desc go
src/Propellor/Property/Obnam.hs view
@@ -38,8 +38,12 @@ -- -- How awesome is that? backup :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Property-backup dir crontimes params numclients = cronjob `describe` desc+backup dir crontimes params numclients = backup' dir crontimes params numclients 	`requires` restored dir params++-- | Does a backup, but does not automatically restore.+backup' :: FilePath -> Cron.CronTimes -> [ObnamParam] -> NumClients -> Property+backup' dir crontimes params numclients = cronjob `describe` desc   where 	desc = dir ++ " backed up by obnam" 	cronjob = Cron.niceJob ("obnam_backup" ++ dir) crontimes "root" "/" $
src/Propellor/Property/Postfix.hs view
@@ -16,7 +16,7 @@ satellite = setup `requires` installed   where 	setup = trivial $ property "postfix satellite system" $ do-		hn <- getHostName+		hn <- asks hostName 		ensureProperty $ Apt.reConfigure "postfix" 			[ ("postfix/main_mailer_type", "select", "Satellite system") 			, ("postfix/root_address", "string", "root")
src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs view
@@ -99,7 +99,9 @@ standardAutoBuilderContainer :: (System -> Docker.Image) -> Architecture -> Int -> TimeOut -> Host standardAutoBuilderContainer dockerImage arch buildminute timeout = Docker.container (arch ++ "-git-annex-builder") 	(dockerImage $ System (Debian Unstable) arch)-	& Apt.stdSourcesList Unstable+	& os (System (Debian Unstable) arch)+	& Apt.stdSourcesList+	& Apt.installed ["systemd"] 	& Apt.unattendedUpgrades 	& buildDepsApt 	& autobuilder (show buildminute ++ " * * * *") timeout True@@ -115,7 +117,9 @@ androidContainer :: (System -> Docker.Image) -> Docker.ContainerName -> Property -> FilePath -> Host androidContainer dockerImage name setupgitannexdir gitannexdir = Docker.container name 	(dockerImage $ System (Debian Stable) "i386")-	& Apt.stdSourcesList Stable+	& os (System (Debian Stable) "i386")+	& Apt.stdSourcesList+	& Apt.installed ["systemd"] 	& User.accountFor builduser 	& File.dirExists gitbuilderdir 	& File.ownerGroup homedir builduser builduser@@ -140,7 +144,9 @@ armelCompanionContainer :: (System -> Docker.Image) -> Host armelCompanionContainer dockerImage = Docker.container "armel-git-annex-builder-companion" 	(dockerImage $ System (Debian Unstable) "amd64")-	& Apt.stdSourcesList Unstable+	& os (System (Debian Unstable) "amd64")+	& Apt.stdSourcesList+	& Apt.installed ["systemd"] 	& Apt.unattendedUpgrades 	-- This volume is shared with the armel builder. 	& Docker.volume gitbuilderdir@@ -156,8 +162,10 @@ armelAutoBuilderContainer :: (System -> Docker.Image) -> Cron.CronTimes -> TimeOut -> Host armelAutoBuilderContainer dockerImage crontimes timeout = Docker.container "armel-git-annex-builder" 	(dockerImage $ System (Debian Unstable) "armel")-	& Apt.stdSourcesList Unstable+	& os (System (Debian Unstable) "armel")+	& Apt.stdSourcesList 	& Apt.unattendedUpgrades+	& Apt.installed ["systemd"] 	& Apt.installed ["openssh-client"] 	& Docker.link "armel-git-annex-builder-companion" "companion" 	& Docker.volumes_from "armel-git-annex-builder-companion"
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -317,3 +317,24 @@ 	]   where 	conf = "/home/znc/.znc/configs/znc.conf"++kiteShellBox :: Property+kiteShellBox = propertyList "kitenet.net shellinabox"+	[ Apt.installed ["shellinabox"]+	, File.hasContent "/etc/default/shellinabox"+		[ "# Deployed by propellor"+		, "SHELLINABOX_DAEMON_START=1"+		, "SHELLINABOX_PORT=443"+		, "SHELLINABOX_ARGS=\"--no-beep --service=/:SSH:kitenet.net\""+		]+		`onChange` Service.restarted "shellinabox"+	, Service.running "shellinabox"+	]++githubBackup :: Property+githubBackup = propertyList "github-backup box"+	[ Apt.installed ["github-backup", "moreutils"]+	, let f = "/home/joey/.github-keys"+	  in File.hasPrivContent f+		`onChange` File.ownerGroup f "joey" "joey"+	]
src/Propellor/Types.hs view
@@ -4,14 +4,13 @@ module Propellor.Types 	( Host(..) 	, Attr-	, SetAttr+	, getAttr 	, Propellor(..) 	, Property(..) 	, RevertableProperty(..) 	, IsProp 	, describe 	, toProp-	, setAttr 	, requires 	, Desc 	, Result(..)@@ -34,18 +33,23 @@ import Propellor.Types.OS import Propellor.Types.Dns --- | Everything Propellor knows about a system: Its properties and--- attributes.-data Host = Host [Property] SetAttr+-- | Everything Propellor knows about a system: Its hostname,+-- properties and attributes.+data Host = Host+	{ hostName :: HostName+	, hostProperties :: [Property]+	, hostAttr :: Attr+	}+	deriving (Show) --- | Propellor's monad provides read-only access to attributes of the--- system.-newtype Propellor p = Propellor { runWithAttr :: ReaderT Attr IO p }+-- | Propellor's monad provides read-only access to the host it's running+-- on, including its attributes.+newtype Propellor p = Propellor { runWithHost :: ReaderT Host IO p } 	deriving 		( Monad 		, Functor 		, Applicative-		, MonadReader Attr +		, MonadReader Host 		, MonadIO 		, MonadCatchIO 		)@@ -57,10 +61,13 @@ 	{ propertyDesc :: Desc 	, propertySatisfy :: Propellor Result 	-- ^ must be idempotent; may run repeatedly-	, propertyAttr :: SetAttr-	-- ^ a property can set an Attr on the host that has the property.+	, propertyAttr :: Attr+	-- ^ a property can set an attribute of the host that has the property. 	} +instance Show Property where+	show p = "property " ++ show (propertyDesc p)+ -- | A property that can be reverted. data RevertableProperty = RevertableProperty Property Property @@ -71,15 +78,15 @@ 	-- | Indicates that the first property can only be satisfied 	-- once the second one is. 	requires :: p -> Property -> p-	setAttr :: p -> SetAttr+	getAttr :: p -> Attr  instance IsProp Property where 	describe p d = p { propertyDesc = d } 	toProp p = p-	setAttr = propertyAttr+	getAttr = propertyAttr 	x `requires` y = Property (propertyDesc x) satisfy attr 	  where-	  	attr = propertyAttr x . propertyAttr y+	  	attr = getAttr y <> getAttr x 		satisfy = do 			r <- propertySatisfy y 			case r of@@ -94,8 +101,8 @@ 	toProp (RevertableProperty p1 _) = p1 	(RevertableProperty p1 p2) `requires` y = 		RevertableProperty (p1 `requires` y) p2-	-- | Return the SetAttr of the currently active side.-	setAttr (RevertableProperty p1 _p2) = setAttr p1+	-- | Return the Attr of the currently active side.+	getAttr (RevertableProperty p1 _p2) = getAttr p1  type Desc = String @@ -129,6 +136,7 @@ 	| Spin HostName 	| Boot HostName 	| Set HostName PrivDataField+	| Dump HostName PrivDataField 	| AddKey String 	| Continue CmdLine 	| Chain HostName
src/Propellor/Types/Attr.hs view
@@ -4,45 +4,62 @@ import qualified Propellor.Types.Dns as Dns  import qualified Data.Set as S-import qualified Data.Map as M+import Data.Monoid --- | The attributes of a host. For example, its hostname.+-- | The attributes of a host. data Attr = Attr-	{ _hostname :: HostName-	, _os :: Maybe System-	, _sshPubKey :: Maybe String+	{ _os :: Val System+	, _sshPubKey :: Val String 	, _dns :: S.Set Dns.Record-	, _namedconf :: M.Map Dns.Domain Dns.NamedConf+	, _namedconf :: Dns.NamedConfMap+	, _dockerattr :: DockerAttr+	}+	deriving (Eq, Show) -	, _dockerImage :: Maybe String+instance Monoid Attr where+	mempty = Attr mempty mempty mempty mempty mempty+	mappend old new = Attr+		{ _os = _os old <> _os new+		, _sshPubKey = _sshPubKey old <> _sshPubKey new+		, _dns = _dns old <> _dns new+		, _namedconf = _namedconf old <> _namedconf new+		, _dockerattr = _dockerattr old <> _dockerattr new+		}++data Val a = Val a | NoVal+	deriving (Eq, Show)++instance Monoid (Val a) where+	mempty = NoVal+	mappend old new = case new of+		NoVal -> old+		_ -> new++fromVal :: Val a -> Maybe a+fromVal (Val a) = Just a+fromVal NoVal = Nothing++data DockerAttr = DockerAttr+	{ _dockerImage :: Val String 	, _dockerRunParams :: [HostName -> String] 	} -instance Eq Attr where+instance Eq DockerAttr where 	x == y = and-		[ _hostname x == _hostname y-		, _os x == _os y-		, _dns x == _dns y-		, _namedconf x == _namedconf y-		, _sshPubKey x == _sshPubKey y--		, _dockerImage x == _dockerImage y+		[ _dockerImage x == _dockerImage y 		, let simpl v = map (\a -> a "") (_dockerRunParams v) 		  in simpl x == simpl y 		] -instance Show Attr where+instance Monoid DockerAttr where+	mempty = DockerAttr mempty mempty+	mappend old new = DockerAttr+		{ _dockerImage = _dockerImage old <> _dockerImage new+		, _dockerRunParams = _dockerRunParams old <> _dockerRunParams new+		}++instance Show DockerAttr where 	show a = unlines-		[ "hostname " ++ _hostname a-		, "OS " ++ show (_os a)-		, "sshPubKey " ++ show (_sshPubKey a)-		, "dns " ++ show (_dns a)-		, "namedconf " ++ show (_namedconf a)-		, "docker image " ++ show (_dockerImage a)+		[ "docker image " ++ show (_dockerImage a) 		, "docker run params " ++ show (map (\mk -> mk "") (_dockerRunParams a)) 		]--newAttr :: HostName -> Attr-newAttr hn = Attr hn Nothing Nothing S.empty M.empty Nothing []--type SetAttr = Attr -> Attr
src/Propellor/Types/Dns.hs view
@@ -3,6 +3,8 @@ import Propellor.Types.OS (HostName)  import Data.Word+import Data.Monoid+import qualified Data.Map as M  type Domain = String @@ -90,3 +92,21 @@ domainHostName (RelDomain d) = Just d domainHostName (AbsDomain d) = Just d domainHostName RootDomain = Nothing++newtype NamedConfMap = NamedConfMap (M.Map Domain NamedConf)+	deriving (Eq, Ord, Show)++-- | Adding a Master NamedConf stanza for a particulr domain always+-- overrides an existing Secondary stanza for that domain, while a+-- Secondary stanza is only added when there is no existing Master stanza.+instance Monoid NamedConfMap where+	mempty = NamedConfMap M.empty+	mappend (NamedConfMap old) (NamedConfMap new) = NamedConfMap $+		M.unionWith combiner new old+	  where+		combiner n o = case (confDnsServerType n, confDnsServerType o) of+			(Secondary, Master) -> o+			_  -> n++fromNamedConfMap :: NamedConfMap -> M.Map Domain NamedConf+fromNamedConfMap (NamedConfMap m) = m
src/config.hs view
@@ -24,7 +24,8 @@ hosts :: [Host] hosts = 	[ host "mybox.example.com"-		& Apt.stdSourcesList Unstable+		& os (System (Debian Unstable) "amd64")+		& Apt.stdSourcesList 		& Apt.unattendedUpgrades 		& Apt.installed ["etckeeper"] 		& Apt.installed ["ssh"]@@ -36,7 +37,9 @@ 		& Cron.runPropellor "30 * * * *"  	-- A generic webserver in a Docker container.-	, Docker.container "webserver" "joeyh/debian-unstable"+	, Docker.container "webserver" "joeyh/debian-stable"+		& os (System (Debian Stable) "amd64")+		& Apt.stdSourcesList 		& Docker.publish "80:80" 		& Docker.volume "/var/www:/var/www" 		& Apt.serviceInstalledRunning "apache2"