type-safe reversions

This commit is contained in:
Joey Hess 2014-04-02 12:13:39 -04:00
parent 7705f65ae2
commit 526bcbf093
10 changed files with 121 additions and 93 deletions

View File

@ -245,14 +245,14 @@ fromMarked marker s
matches = filter (marker `isPrefixOf`) $ lines s
boot :: [Property] -> IO ()
boot props = do
boot ps = do
sendMarked stdout statusMarker $ show Ready
reply <- hGetContentsStrict stdin
makePrivDataDir
maybe noop (writeFileProtected privDataLocal) $
fromMarked privDataMarker reply
ensureProperties props
ensureProperties ps
addKey :: String -> IO ()
addKey keyid = exitBool =<< allM id [ gpg, gitadd, gitcommit ]

View File

@ -58,14 +58,6 @@ property `onChange` hook = Property (propertyDesc property) $ do
return $ r <> r'
_ -> return r
-- | Indicates that the first property can only be satisfied once
-- the second is.
requires :: Property -> Property -> Property
x `requires` y = combineProperties (propertyDesc x) [y, x]
describe :: Property -> Desc -> Property
describe p d = p { propertyDesc = d }
(==>) :: Desc -> Property -> Property
(==>) = flip describe
infixl 1 ==>
@ -76,3 +68,17 @@ check c property = Property (propertyDesc property) $ ifM c
( ensureProperty property
, return NoChange
)
-- | Undoes the effect of a property.
revert :: RevertableProperty -> RevertableProperty
revert (RevertableProperty p1 p2) = RevertableProperty p2 p1
-- | Starts a list of Properties
props :: [Property]
props = []
-- | Adds a property to the list.
-- Can add both Properties and RevertableProperties.
(&) :: IsProp p => [Property] -> p -> [Property]
ps & p = toProp p : ps
infixl 1 &

View File

@ -129,16 +129,18 @@ autoRemove :: Property
autoRemove = runApt ["-y", "autoremove"]
`describe` "apt autoremove"
unattendedUpgrades :: Bool -> Property
unattendedUpgrades enabled =
(if enabled then installed else removed) ["unattended-upgrades"]
`onChange` reConfigure "unattended-upgrades"
[("unattended-upgrades/enable_auto_updates" , "boolean", v)]
`describe` ("unattended upgrades " ++ v)
-- | Enables unattended upgrades. Revert to disable.
unattendedUpgrades :: RevertableProperty
unattendedUpgrades = RevertableProperty (go True) (go False)
where
v
| enabled = "true"
| otherwise = "false"
go enabled = (if enabled then installed else removed) ["unattended-upgrades"]
`onChange` reConfigure "unattended-upgrades"
[("unattended-upgrades/enable_auto_updates" , "boolean", v)]
`describe` ("unattended upgrades " ++ v)
where
v
| enabled = "true"
| otherwise = "false"
-- | Preseeds debconf values and reconfigures the package so it takes
-- effect.

View File

@ -39,33 +39,27 @@ installed = Apt.installed ["docker.io"]
-- | 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.
--
-- Reverting this property ensures that the container is stopped and
-- removed.
docked
:: (HostName -> ContainerName -> Maybe (Container))
-> HostName
-> ContainerName
-> Property
-> RevertableProperty
docked findc hn cn = findContainer findc hn cn $
\(Container image containerprops) ->
provisionContainer cid
`requires`
runningContainer cid image containerprops
where
cid = ContainerId hn cn
-- | Ensures that a docker container is no longer running.
unDocked
:: (HostName -> ContainerName -> Maybe (Container))
-> HostName
-> ContainerName
-> Property
unDocked findc hn cn = findContainer findc hn cn $
\(Container image _containerprops) ->
Property ("undocked " ++ fromContainerId cid) $
report <$> mapM id
[ stopContainer cid
, removeContainer cid
, removeImage image
]
let setup = provisionContainer cid
`requires`
runningContainer cid image containerprops
teardown =
Property ("undocked " ++ fromContainerId cid) $
report <$> mapM id
[ stopContainer cid
, removeContainer cid
, removeImage image
]
in RevertableProperty setup teardown
where
cid = ContainerId hn cn
@ -73,15 +67,16 @@ findContainer
:: (HostName -> ContainerName -> Maybe (Container))
-> HostName
-> ContainerName
-> (Container -> Property)
-> Property
-> (Container -> RevertableProperty)
-> RevertableProperty
findContainer findc hn cn mk = case findc hn cn of
Nothing -> containerDesc (ContainerId hn cn) $ Property "" $ do
warningMessage $ "missing definition for docker container \"" ++ fromContainerId cid
return FailedChange
Nothing -> RevertableProperty cantfind cantfind
Just container -> mk container
where
cid = ContainerId hn cn
cantfind = containerDesc (ContainerId hn cn) $ Property "" $ do
warningMessage $ "missing definition for docker container \"" ++ fromContainerId cid
return FailedChange
-- | Causes *any* docker images that are not in use by running containers to
-- be deleted. And deletes any containers that propellor has set up

View File

@ -12,6 +12,33 @@ data Property = Property
, propertySatisfy :: IO Result
}
data RevertableProperty = RevertableProperty Property Property
class IsProp p where
-- | Sets description.
describe :: p -> Desc -> p
toProp :: p -> Property
-- | Indicates that the first property can only be satisfied
-- once the second one is.
requires :: p -> Property -> p
instance IsProp Property where
describe p d = p { propertyDesc = d }
toProp p = p
x `requires` y = Property (propertyDesc x) $ do
r <- propertySatisfy y
case r of
FailedChange -> return FailedChange
_ -> propertySatisfy x
instance IsProp RevertableProperty where
-- | Sets the description of both sides.
describe (RevertableProperty p1 p2) d =
RevertableProperty (describe p1 d) (describe p2 ("not " ++ d))
toProp (RevertableProperty p1 _) = p1
(RevertableProperty p1 p2) `requires` y =
RevertableProperty (p1 `requires` y) p2
type Desc = String
data Result = NoChange | MadeChange | FailedChange
@ -74,3 +101,5 @@ data PrivDataField
| SshPrivKey UserName
| Password UserName
deriving (Read, Show, Ord, Eq)

3
TODO
View File

@ -6,9 +6,6 @@
* --spin needs 4 ssh connections when bootstrapping a new host
that does not have the git repo yet. Should be possible to get that
down to 1.
* Make a way to express that a Property can be reverted (ie, installing a
packages reverses to removing it). Then `reverted property` can be
used to disable old properties.
* Currently only Debian and derivatives are supported by most Properties.
One way to improve that would be to parameterize Properties with a
Distribution witness.

View File

@ -2,7 +2,7 @@
-- the propellor program.
--
-- This is the live config file used by propellor's author.
-- For a simpler starting point, see config.hs.simple.
-- For a simpler starting point, see simple-config.hs
import Propellor
import Propellor.CmdLine
@ -31,33 +31,31 @@ main = defaultMain [host, Docker.containerProperties container]
--
-- Edit this to configure propellor!
host :: HostName -> Maybe [Property]
host hostname@"clam.kitenet.net" = Just
[ cleanCloudAtCost hostname
, standardSystem Unstable
, Apt.unattendedUpgrades True
, Network.ipv6to4
host hostname@"clam.kitenet.net" = Just $ props
& cleanCloudAtCost hostname
& standardSystem Unstable
& Apt.unattendedUpgrades
& Network.ipv6to4
& Apt.installed ["git-annex", "mtr"]
-- Clam is a tor bridge, and an olduse.net shellbox and other
-- fun stuff.
, Tor.isBridge
, JoeySites.oldUseNetshellBox
, Docker.configured
, File.dirExists "/var/www"
--, Docker.docked container hostname "webserver"
, Docker.garbageCollected
, Docker.unDocked container hostname "amd64-git-annex-builder"
, Apt.installed ["git-annex", "mtr"]
& Tor.isBridge
& JoeySites.oldUseNetshellBox
& Docker.configured
& File.dirExists "/var/www"
& revert (Docker.docked container hostname "webserver")
& revert (Docker.docked container hostname "amd64-git-annex-builder")
& Docker.garbageCollected
-- Should come last as it reboots.
, Apt.installed ["systemd-sysv"] `onChange` Reboot.now
]
host hostname@"orca.kitenet.net" = Just
[ Hostname.set hostname
, standardSystem Unstable
, Apt.unattendedUpgrades True
, Docker.configured
, Docker.unDocked container hostname "amd64-git-annex-builder"
, Docker.unDocked container hostname "i386-git-annex-builder"
, Docker.garbageCollected
]
& Apt.installed ["systemd-sysv"] `onChange` Reboot.now
host hostname@"orca.kitenet.net" = Just $ props
& Hostname.set hostname
& standardSystem Unstable
& Apt.unattendedUpgrades
& Docker.configured
& revert (Docker.docked container hostname "amd64-git-annex-builder")
& revert (Docker.docked container hostname "i386-git-annex-builder")
& Docker.garbageCollected
-- add more hosts here...
--host "foo.example.com" =
host _ = Nothing
@ -70,16 +68,15 @@ container _host name
(image $ System (Debian Unstable) "amd64")
[ Docker.publish "8080:80"
, Docker.volume "/var/www:/var/www"
, Docker.inside
[ serviceRunning "apache2"
, Docker.inside $ props
& serviceRunning "apache2"
`requires` Apt.installed ["apache2"]
]
]
| "-git-annex-builder" `isSuffixOf` name =
let arch = takeWhile (/= '-') name
in Just $ Docker.containerFrom
(image $ System (Debian Unstable) arch)
[ Docker.inside [ GitAnnexBuilder.builder arch "15 * * * *" ] ]
[ Docker.inside $ props & GitAnnexBuilder.builder arch "15 * * * *" ]
| otherwise = Nothing
-- | Docker images I prefer to use.

5
debian/changelog vendored
View File

@ -7,5 +7,10 @@ propellor (0.2) UNRELEASED; urgency=low
to pull commits from git repositories not signed with that key.
This allows propellor to be securely used with public, non-encrypted
git repositories without the possibility of MITM.
* Added support for type-safe reversions. Only some properties can be
reverted; the type checker will tell you if you try something that won't
work.
* New syntactic sugar for building a list of properties, including
revertable properties.
-- Joey Hess <joeyh@debian.org> Tue, 01 Apr 2014 15:05:00 -0400

View File

@ -17,7 +17,7 @@ Extra-Source-Files:
Makefile
debian/changelog
debian/README
config.hs.simple
simple-config.hs
Synopsis: property-based host configuration management in haskell
Description:
Propellor enures that the system it's run in satisfies a list of

View File

@ -23,19 +23,18 @@ main = defaultMain [host, Docker.containerProperties container]
--
-- Edit this to configure propellor!
host :: HostName -> Maybe [Property]
host hostname@"mybox.example.com" = Just
[ Apt.stdSourcesList Unstable
host hostname@"mybox.example.com" = Just $ props
& Apt.stdSourcesList Unstable
`onChange` Apt.upgrade
, Apt.unattendedUpgrades True
, Apt.installed ["etckeeper"]
, Apt.installed ["ssh"]
, User.hasSomePassword "root"
, Network.ipv6to4
, Docker.docked container hostname "webserver"
& Apt.unattendedUpgrades
& Apt.installed ["etckeeper"]
& Apt.installed ["ssh"]
& User.hasSomePassword "root"
& Network.ipv6to4
& Docker.docked container hostname "webserver"
`requires` File.dirExists "/var/www"
, Docker.garbageCollected
, Cron.runPropellor "30 * * * *"
]
& Docker.garbageCollected
& Cron.runPropellor "30 * * * *"
-- add more hosts here...
--host "foo.example.com" =
host _ = Nothing
@ -44,12 +43,10 @@ host _ = Nothing
-- can vary by hostname where it's used, or be the same everywhere.
container :: HostName -> Docker.ContainerName -> Maybe (Docker.Container)
container _ "webserver" = Just $ Docker.containerFrom "joeyh/debian-unstable"
(image $ System (Debian Unstable) "amd64")
[ Docker.publish "80:80"
, Docker.volume "/var/www:/var/www"
, Docker.inside
[ serviceRunning "apache2"
, Docker.inside $ props
& serviceRunning "apache2"
`requires` Apt.installed ["apache2"]
]
]
container _ _ = Nothing