Sélectionner une page

Augeas is awesome

As intensive Puppet users, updating only certain key/values of system configuration files is extremely practical.

And this is what augeas does by convert existing files into an « abstraction », working on a virtual representation of the file made available as a tree and then writing changes back into the file.

# augtool print /files/etc/passwd
/files/etc/passwd
/files/etc/passwd/root
/files/etc/passwd/root/password = "x"
/files/etc/passwd/root/uid = "0"
/files/etc/passwd/root/gid = "0"
/files/etc/passwd/root/name = "root"
/files/etc/passwd/root/home = "/root"
/files/etc/passwd/root/shell = "/bin/bash"

Yet Augeas is very sensitive to syntax errors

Alas, sometimes Augeas doesn’t work and won’t say why. The augtool command -part of the augeas-tools Debian package- will just silently fail reading a file.

In that kind of case, you can of course resort to a well-known method « Cut 50% of content and see if the bug occurs, otherwise cut the other 50%, and repeat until you find the bug. »

But in the age of advanced testing methods, this is silly and there is a better method.

Introducing augparse

Augeas works with « lenses » which are in charge of converting the file into a tree and vice-versa.

Augparse -also a member of the augeas-tools package- is the tool you can use to write and test lenses.

On Debian, the lenses are provided with their corresponding tests, found in /usr/share/augeas/lenses/{dist/}tests

# cat /usr/share/augeas/lenses/dist/tests/test_webmin.aug
module Test_webmin =

let conf = "port=10000
realm=Webmin Server
denyfile=\.pl$
"

test Webmin.lns get conf =
{ "port" = "10000" }
{ "realm" = "Webmin Server" }
{ "denyfile" = "\.pl$" }

In this example, we see how the augparse syntax works.

  • First, the corresponding module is emptied for a clean test environment
  • Then the let keyword is used to define a multiline string, representing the file’s content.
  • Finally a test is declared, using a lens executed against the string.

Finding syntax errors with augparse

Now, finding where your file breaks is really easy:

  1. Make a copy of the test file corresponding to the file you want to parse. Ex: test_webmin.aug
  2. Edit this copy to add your actual configuration file content. Replace the first let variable value with the file’s content, but carefully remove or escape quotes.
  3. Replace the rest of the test. Replace all other variables and tests by a single test running against your own content. Do not attempt to match a given tree (i.e. {« key » = « value »}), simply match a question mark, which will output the parsed tree. Ex: test Webmin.lns get conf = ?
  4. Use augparse to run the test. You will test your actual configuration file’s content and you shall be rewarded with error lines, invalid characters, or the actual tree if the file is OK.

Hands on

We had a postfix which refused to be parsed.

I copied /usr/share/augeas/lenses/dist/tests/test_postfix_master.aug to /tmp and replaced its content by:

module Test_postfix_master =

let conf = "# AUTO GENERATED FILE
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page 
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtp      inet  n       -       -       -       -       smtpd
  -o content_filter=smtp-amavis:[127.0.0.1]:10024
  -o receive_override_options=no_address_mappings
# ... SNIP MASTER.CF OTHER CONTENT ...
"
test Postfix_Master.lns get conf = ?

At this point and after fixing quotes escaping, I encountered the following error when running:

# augparse /tmp/test_postfix_master.aug

/tmp/test_postfix_master.aug:117.0-.36:exception thrown in test
/tmp/test_postfix_master.aug:117.5-.32:exception: Iterated lens matched less than it should
    Lens: /usr/share/augeas/lenses/dist/postfix_master.aug:49.17-.40:
    Error encountered at 16:0 (599 characters into string)
    <      -       -       smtpd\n|=|  -o content_filter=smtp-ama>

    Tree generated so far:
    /#comment[1] = "AUTO GENERATED FILE"
/#comment[2] = "Postfix master process configuration file.  For details on the format"
/#comment[3] = "of the file, see the master(5) manual page"
/#comment[4] = "=========================================================================="
/#comment[5] = "service type  private unpriv  chroot  wakeup  maxproc command + args"
/#comment[6] = "(yes)   (yes)   (yes)   (never) (100)"
/#comment[7] = "=========================================================================="
/smtp
/smtp/type = "inet"
/smtp/private = "n"
/smtp/unprivileged = "-"
/smtp/chroot = "-"
/smtp/wakeup = "-"
/smtp/limit = "-"
/smtp/command = "smtpd"

And there we are, the invalid line for augeas was -o content_filter=smtp-amavis:[127.0.0.1]:10024, removing the brackets worked.

After that, our puppet run worked fine thanks to augeas reading correctly the file. Thanks, lens debugging!

Image copyright Steven Depolo | CC BY 2.0