# task.check – Soft checks with generic patterns, final hard checks for type, masters, and listen-on # 1. Convert original BIND config to dot‑flat regexp: .* code: < $line { next if $line ~~ /^ \s* $/ || $line ~~ /^ \s* '#'/ || $line ~~ /^ \s* ';'/; if $line ~~ /^ \s* '[' \s* ( <-[ \] ]>+ ) \s* ']' \s* $/ { $section = $0.subst(/ \s* '"' | '"' \s* /, '', :g).subst(/\s+/, '.'); } elsif $line ~~ /^ \s* ( <-[ = ; ]>+ ) \s* '=' \s* ( <-[ ; ]>*? ) \s* ';'? \s* $/ { if $section { @flat.push: "$section.{$0.trim} = {$1.trim}"; } } } my $flat-file = '/tmp/bind-flat.conf'; spurt $flat-file, @flat.join("\n"); CODE source: /tmp/bind-flat.conf # 2. options.directory – soft check ~regexp: options \. directory \s* \= \s* ( <-[ ; ]>+ ) \s* \;? code: < capture()[0] }); } CODE generator: < @c { for @c -> $c { my $ok = $c[0] ne ''; say "assert: {$ok} options.directory is '$c[0]'"; } } CODE # 3. options.listen-on – soft check, must be 127.0.0.1 ~regexp: options \. listen\-on \s* \= \s* \{ \s* ( <-[ } ; ]>+ ) \s* [ ; \s* ]? \} \s* \;? code: < @c { for @c -> $c { my $ip = $c[0].trim; my $ok = $ip eq '127.0.0.1'; say "assert: {$ok} options.listen-on is '$ip' (must be 127.0.0.1)"; } } else { say "assert: 0 options.listen-on not defined"; } CODE # 4. zone..type – soft, collect zone names and types ~regexp: zone \. ( <-[ \= ; ]>+ ) \. type \s* \= \s* ( <-[ ; ]>+ ) \s* \;? code: < @all { my $state = get_state(); $state //= []; $state //= {}; for @all -> $c { my $zone = $c[0]; $state.push($zone) unless $state.grep($zone); $state{$zone} = $c[1]; } update_state($state); } CODE generator: < @c { for @c -> $c { my ($zone, $type) = $c[0], $c[1]; my $valid = $type eq 'master' || $type eq 'slave'; say "assert: {$valid} zone $zone type is '$type'"; } } CODE # 5. zone..file – soft ~regexp: zone \. ( <-[ \= ; ]>+ ) \. file \s* \= \s* ( <-[ ; ]>+ ) \s* \;? code: < @c { for @c -> $c { my ($zone, $file) = $c[0], $c[1]; my $nonempty = $file ne ''; say "assert: {$nonempty} zone $zone file is '$file'"; } } CODE # 6. zone..masters – soft, mark zones having a masters field ~regexp: zone \. ( <-[ \= ; ]>+ ) \. masters \s* \= \s* ( <-[ ; ]>+ ) \s* \;? code: < @all { my $state = get_state(); $state //= []; for @all -> $c { my $zone = $c[0]; $state.push($zone) unless $state.grep($zone); } update_state($state); } CODE generator: < @c { for @c -> $c { say "assert: 1 zone $c[0] masters defined"; } } CODE # 7. Final hard check: all zones must be master or slave generator: < // {}; if %types { my @invalid = %types.keys.grep: { %types{$_} ne 'master' && %types{$_} ne 'slave' }; if @invalid { say "assert: 0 zones with invalid type: {@invalid.join(', ')}"; } else { say "assert: 1 all zones have type master or slave"; } } else { say "assert: 1 no zones defined"; } CODE # 8. Final hard check: all zones must have a masters field generator: < // []; my @with = $state // []; my @missing = @all.grep: { $_ ∉ @with }; if @missing { say "assert: 0 zones missing masters: {@missing.join(', ')}"; } else { say "assert: 1 all zones have masters field set"; } CODE