From 4020a3e7077b285d888845ac508b233624b17ed0 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 19 Apr 2015 12:51:12 -0400 Subject: [PATCH] new page --- doc/documentation.mdwn | 1 + doc/haskell_newbie.mdwn | 3 +- doc/writing_properties.mdwn | 82 +++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 doc/writing_properties.mdwn diff --git a/doc/documentation.mdwn b/doc/documentation.mdwn index eccdda5..99f61c0 100644 --- a/doc/documentation.mdwn +++ b/doc/documentation.mdwn @@ -5,6 +5,7 @@ Other documentation: * [[man page|usage]] * [[Haskell Newbie]] +* [[Writing Properties]] * [[Centralized Git Repository]] * [[Components]] * [[Contributing]] diff --git a/doc/haskell_newbie.mdwn b/doc/haskell_newbie.mdwn index 39a62f4..38da5ab 100644 --- a/doc/haskell_newbie.mdwn +++ b/doc/haskell_newbie.mdwn @@ -114,7 +114,8 @@ That's really all there is to configuring Propellor. Once you have a `config.hs` ready to try out, you can run `propellor --spin $host` on one of the hosts configured in it. -See the [[README]] for a further quick start. +See the [[README]] for a further quick start and [[Writing Properties]] +for guidance on extending propellor with your own custom properties. (If you'd like to learn a little Haskell after all, check out [Learn You a Haskell for Great Good](http://learnyouahaskell.com/).) diff --git a/doc/writing_properties.mdwn b/doc/writing_properties.mdwn new file mode 100644 index 0000000..c7183e0 --- /dev/null +++ b/doc/writing_properties.mdwn @@ -0,0 +1,82 @@ +Propellor comes with a lot of properties you can use. But eventually, +you'll want to write a property of your own. + +This isn't hard. Often propellor has some properties you can use to build +the property you want. Need to modify the content of a file? Use any of +the properties in +[Propellor.Property.File](http://hackage.haskell.org/package/propellor-2.2.1/docs/Propellor-Property-File.htm) +Need to run some commands? Use [Propellor.Property.Cmd](http://hackage.haskell.org/package/propellor-2.2.1/docs/Propellor-Property-Cmd.html). + +To combine properties, the easiest way is to use `requires`. + + someproperty `requires` otherproperty + +[Propellor.Property.List](http://hackage.haskell.org/package/propellor-2.2.1/docs/Propellor-Property-List.html) +has a `propertyList` combinator that's also useful. + +[Propellor.Property](http://hackage.haskell.org/package/propellor-2.2.1/docs/Propellor-Property.html) +has some other functions to modify Properties in useful ways. +For example, `check` makes a Property call an `IO Bool` to check if the +Property needs be run. + +## example: User.hasLoginShell + +> As far as I can tell there is no easy way to set a user's +> login shell. A Property User.hasLoginShell, which ensures +> that a user has a specified login shell and that said shell +> is in /etc/shells would be really helpful. Sadly, I lack the +> skills to put this together myself :( -- weinzwang + +Propellor makes it very easy to put together a property like this. + +Let's start with a property that combines the two properties you mentioned: + + hasLoginShell :: UserName -> FilePath -> Property + hasLoginShell user shell = shellSetTo user shell `requires` shellEnabled shell + +The shellEnabled property can be easily written using propellor's file +manipulation properties. + + -- Need to add an import to the top of the source file. + import qualified Propellor.Property.File as File + + shellEnabled :: FilePath -> Property + shellEnabled shell = "/etc/shells" `File.containsLine` shell + +And then, we want to actually change the user's shell. The `chsh(1)` +program can do that, so we can simply tell propellor the command line to +run: + + shellSetTo :: UserName -> FilePath -> Property + shellSetTo user shell = cmdProperty "chsh" ["--shell", shell, user] + +The only remaining problem with this is that shellSetTo runs chsh every +time, and propellor will always display that it's made a change each time +it runs, even when it didn't really do much. Now, there's an easy way to +avoid that problem, we could just tell propellor that it's a trivial +property, and then it will run chsh every time and not think it made any +change: + + shellSetTo :: UserName -> FilePath -> Property + shellSetTo user shell = trivial $ + cmdProperty "chsh" ["--shell", shell, user] + +But, it's not much harder to do this right. Let's make the property +check if the user's shell is already set to the desired value and avoid +doing anything in that case. + + shellSetTo :: UserName -> FilePath -> Property + shellSetTo user shell = check needchangeshell $ + cmdProperty "chsh" ["--shell", shell, user] + where + needchangeshell = do + currshell <- userShell <$> getUserEntryForName user + return (currshell /= shell) + +And that will probably all work, although I've not tested it. You might +want to throw in some uses of `describe` to give the new properties +more useful descriptions. + +I hope this has been helpful as an explanation of how to add properties to +Propellor, and if you get these properties to work, a patch adding them +to Propellor.User would be happily merged.