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
properties versus actions

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...

Comment by Anthony
comment 2

@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. :)

Comment by joeyh.name
comment 3

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:

  • to be able to describe different aspects of the system (what distro, what packages, contents of files, user setup, shell snippets to run, whatever)
  • to be able to split your configuration into modules
  • to generate actions from a description that will set the system up according to the description

You could "type" some of your descriptions into:

  • distro (Debian unstable)
  • distro configuration (debconf settings, installed debs, selected alternatives, system user ids...)
  • package configuration (configure ldap, apache, pam, etc -- some bits might be systemd specific)
  • local user configuration (populate /home, user account names/ids/shells, ...)
  • local software (/usr/local/, /opt/)
  • local content (/srv/*, /var/www, ...)

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...

Comment by Anthony