Propellor ensures that a list of properties about a system are satisfied. But requirements change, and so you might want to revert a property that had been set up before.
For example, I had a system with a webserver container:
Docker.docked container hostname "webserver"
I don't want a web server there any more. Rather than having a separate property to stop it, wouldn't it be nice to be able to say:
revert (Docker.docked container hostname "webserver")
I've now gotten this working. The really fun part is, some properies support reversion, but other properties certianly do not. Maybe the code to revert them is not worth writing, or maybe the property does something that cannot be reverted.
For example, Docker.garbageCollected
is a property that makes sure there
are no unused docker images wasting disk space. It can't be reverted.
Nor can my personal standardSystem Unstable
property, which amoung other
things upgrades the system to unstable and sets up my home directory..
I found a way to make Propellor statically check if a property can be
reverted at compile time. So revert Docker.garbageCollected
will fail
to type check!
The tricky part about implementing this is that the user configures Propellor with a list of properties. But now there are two distinct types of properties, revertable ones and non-revertable ones. And Haskell does not support heterogeneous lists..
My solution to this is a typeclass and some syntactic sugar operators. To build a list of properties, with individual elements that might be revertable, and others not:
props
& standardSystem Unstable
& revert (Docker.docked container hostname "webserver")
& Docker.docked container hostname "amd64-git-annex-builder"
& Docker.garbageCollected
Maybe you could distinguish between properties and actions? A property being a description of the system, and an action being a change you make to a description (which may then make a property true or false).
I think maintaining a "description of a system" makes more sense long term. That's what I'd like to have checked into a git repo, for instance.
A list of actions is what you'd need to bring up a system from nothing, or to upgrade/downgrade it (stable -> testing, webserver to not-a-webserver), of course.
You could obviously generate a list of actions to take to convert from one description of a system to another.
Being able to "apply a description" to an existing system could be done with those steps if you also had something that would generate a description from a running system. That would be a really cool feature to have for converting to "propellor", just run the "create-description" thing on your hosts, check them into git, and you can recreate your system just by hitting a button...
@Anthony, these are good thoughts. I do see the Properties as being a description of a system. But one problem as it stands now is that since a Property includes an IO action which is a closure over any configuration, there is no way to serialize/deserialize Properties. Which is necessary for the kind of thing you are thinking of, and some ideas I have had too.
Concrete suggestions for better types in this area would be appreciated. :)
hmph, no "reply to comment" button? weeeaaak.
So I just deleted a bunch of pseudo-haskell that demonstrated I've no idea what I'm talking about.
Anyway, what you (I) want is:
You could "type" some of your descriptions into:
You might want to be able to say "There could be random stuff in /home" as part of a description, if you don't want "make this machine conform to this description" to delete everything in /home/aj.bak, eg.
I'm not sure how you'd build a good datastructure out of that -- you want to be able to configure lots of packages on a system, but you don't want to be able to define the system as running both Debian and Ubuntu (probably?). It might be okay to only validate that at runtime though?
Actions would then be defined by changes in configuration. So you'd have to have functions to go from a description that says "Oracle is installed in /opt", to one that doesn't. Maybe something like:
matchConfig :: [Description] -> [Description] -> [Actions] createAction :: Description -> [Action] removeAction :: Description -> [Action] configToAction :: Description -> Description -> [Description] -> ([Action], [Description])
matchConfig [] [] = [] matchConfig [] b = createAction (head b) : matchConfig [] (tail b) matchConfig a [] = removeAction (head a) : matchConfig (tail a) [] matchConfig a b = actions ++ (matchconfig (tail a) newb) where (actions, newb) = configToAction (head a) (head b) (tail b)
createAction InstallOracle = ... removeAction InstallOracle = ...
configToAction InstallOracle InstallOracle t = ([], t) configToAction InstallOracle h [] = ([removeAction InstallOracle], []) configToAction InstallOracle h t = (a, h:tt) where (a,tt) = configToAction InstallOracle (head t) (tail t)
...
I'm not sure I even know where I'm trying to go with this...