DEV Community

Alexey Melezhik
Alexey Melezhik

Posted on

Validating configuration files with Raku and Sparrow Task::Check DSL

In real world we have a lot of pure structured or structured in hard to parse formats configuration files:

/etc/default/nginx, /etc/default/grub, logrotate.conf, sysctl.conf - to name a few.

In configuration management it's not only important to maintain specific configurations but also validate those changes to make it sure we don't break things.

Sparrow - is an automation tool written on Raku that allow to make such a validation by leveraging Task::Check DSL rules which underneath are just Raku regular expressions.

In the rest of this post we take a look at real life example.


Grub

Consider linux GRUB boot loader file /etc/default/grub with following configuration:

GRUB_DEFAULT=0
#GRUB_HIDDEN_TIMEOUT=0
GRUB_HIDDEN_TIMEOUT_QUIET=true
GRUB_TIMEOUT=2
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="rootfstype=ext4 quiet splash acpi_osi="
GRUB_CMDLINE_LINUX=""
GRUB_ENABLE_CRYPTODISK=true

# Uncomment to enable BadRAM filtering, modify to suit your needs
# This works with Linux (no patch required) and with any kernel that obtains
# the memory map information from GRUB (GNU Mach, kernel of FreeBSD ...)
#GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef"

# Uncomment to disable graphical terminal (grub-pc only)
#GRUB_TERMINAL=console

# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `vbeinfo'
#GRUB_GFXMODE=640x480

# Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux
#GRUB_DISABLE_LINUX_UUID=true

# Uncomment to disable generation of recovery mode menu entries
#GRUB_DISABLE_RECOVERY="true"

# Uncomment to get a beep at grub start
#GRUB_INIT_TUNE="480 440 1"

GRUB_DISABLE_OS_PROBER="true"
Enter fullscreen mode Exit fullscreen mode

Though configuration format is quite simple, based on VAR=value semantics, it still allows some freedom in specification.

For example to enable variables we could use one of many forms - yes/"yes", true/"true" and just 1. Another challenge is to check that some variable are disabled - they might be just commented or “switched off” via negation form, e.g. VAR=0,false,""


As rigorous operations we want to validate a couple of things:

  • Boot on crypted disks is supported by setting GRUB_ENABLE_CRYPTODISK to one of the values: 1,yes,true and optional quoted form - "yes","true"

  • OS prober capability is disabled or in other words GRUB_DISABLE_OS_PROBER is not set by using any of forms - 1,yes,true,"yes","true"

Before go parsing let's create simple Raku program (Sparrow task), that dumps out configuration removing empty and commented lines, the whole output will be placed inside >>>, <<< markers for visibility:

task.raku

#!raku

my $path = config()<path>;

say "validate config {$path} ...";

say ">>>";

my @out;

for $path.IO.lines -> $i {
  $i.chomp;
  next unless $i ~~ /\S+/;
  @out.push($i) unless $i ~~ /^ \s* '#'/;
};

say sort(@out).join("\n");
say "<<<";

Enter fullscreen mode Exit fullscreen mode

Now let's write verification scenario by creating rules in Sparrow::Task::Check DSL format:

task.check

note: validate config

between: {'>>>'}  {'<<<'}

  note: allow to boot on crypted disks
  regexp: ^^ \s* "GRUB_ENABLE_CRYPTODISK=" (<[y 1 true]>  | '"true"' | '"yes"' )

  note: never allow to enable os prober
  !regexp: ^^ \s* "GRUB_DISABLE_OS_PROBER=" (<[y 1 true]> | '"true"' | '"yes"' )

end:
Enter fullscreen mode Exit fullscreen mode

The DSL code has there major parts:

  • between: {'>>>'} {'<<<'}

Setting a context for a search in between >>> ... <<< delimiter, this is an example of so called search context modifiers, in this case - Range modifier.

In other words this is precaution that we only search within specific boundaries of text output

  • regexp: ^^ \s* "GRUB_ENABLE_CRYPTODISK=" (<[y 1 true]> | '"true"' | '"yes"' )

Verifying that we have GRUB_ENABLE_CRYPTODISK set to one of values (y,1,true,"true","yes").

regexp: marker means Raku regular expression is used for validation.

  • note: never allow to enable os prober !regexp: ^^ \s* "GRUB_DISABLE_OS_PROBER=" (<[y 1 true]> | '"true"' | '"yes"' )

Verifying we DON'T(*) have GRUB_ENABLE_CRYPTODISK set to one of values (y,1,true,"true","yes"), in other words that GRUB_ENABLE_CRYPTODISK is disabled.

(*) ! sign before regexp: marker means negation logic.

Note: expressions are human readable comments that are showed up in a report to help us correlate every check to business logic

Test report

Now let's run our script and get a result of test. As this is Sparrow scenario, we use s6 - Sparrow CLI runner:

#!bash
s6 --task-run .@path=/etc/default/grub
Enter fullscreen mode Exit fullscreen mode
14:09:20 :: [sparrowtask] - run sparrow task .@path=/etc/default/grub
14:09:20 :: [sparrowtask] - run thing .
[task run: task.raku - .]
[task stdout]
14:09:21 :: validate config examples/grub ...
14:09:21 :: >>>
14:09:21 :: GRUB_CMDLINE_LINUX=""
14:09:21 :: GRUB_CMDLINE_LINUX_DEFAULT="rootfstype=ext4 quiet splash acpi_osi="
14:09:21 :: GRUB_DEFAULT=0
14:09:21 :: GRUB_DISABLE_OS_PROBER="true"
14:09:21 :: GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
14:09:21 :: GRUB_ENABLE_CRYPTODISK=true
14:09:21 :: GRUB_HIDDEN_TIMEOUT_QUIET=true
14:09:21 :: GRUB_TIMEOUT=2
14:09:21 :: <<<
[task check]
# validate config
# allow to boot on crypted disks
stdout match (r) <^^ \s* "GRUB_ENABLE_CRYPTODISK=" (<[y 1 true]>  | '"true"' | '"yes"' )> True
# never allow to enable os prober
stdout match (r) <!^^ \s* "GRUB_DISABLE_OS_PROBER=" (<[y 1 true]> | '"true"' | '"yes"' )> False
=================
TASK CHECK FAIL

Enter fullscreen mode Exit fullscreen mode
  • PS for colorful report please click here

Expectedly we have following results:

  • GRUB_ENABLE_CRYPTODISK check passed
  • GRUB_DISABLE_OS_PROBER fails

Pretty impressive results considering such a little amount of code has been written!


Sparrow Task::Checks is comprehensive and super flexible tool allowing one to write validation scenarios for arbitrary data formats and complexity, check out documentation pages to know more.

That is it. Thanks for reading

Top comments (2)

Collapse
 
acrion profile image
Stefan Zipproth

Really appreciate this practical approach! The way you handle different value formats (like yes/"yes", true/"true", 1) in the validation rules is super clever. This solves a real pain point in configuration management. Thanks for sharing with such clear examples!

Collapse
 
melezhik profile image
Alexey Melezhik

Thanks for valuable feedback Stefan!