summaryrefslogtreecommitdiff
path: root/doc/writing_properties.mdwn
diff options
context:
space:
mode:
Diffstat (limited to 'doc/writing_properties.mdwn')
-rw-r--r--doc/writing_properties.mdwn82
1 files changed, 82 insertions, 0 deletions
diff --git a/doc/writing_properties.mdwn b/doc/writing_properties.mdwn
new file mode 100644
index 00000000..c7183e09
--- /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.