timproved zone configuration: directories - gitzone - git-based zone management tool for static and dynamic domains HTML git clone https://git.parazyd.org/gitzone DIR Log DIR Files DIR Refs --- DIR commit ddf67d329f5f9cd8adcd6b156cac5c093f652292 DIR parent 568aef2c60e8b76545c8d5600f6a08fe25d88189 HTML Author: tg(x) <*@tg-x.net> Date: Sun, 13 Feb 2011 15:26:51 +0100 improved zone configuration: directories Diffstat: M bin/gitzone | 118 +++++++++++++++++-------------- M etc/gitzone.conf | 28 +++++++++++++++++----------- 2 files changed, 81 insertions(+), 65 deletions(-) --- DIR diff --git a/bin/gitzone b/bin/gitzone t@@ -12,7 +12,7 @@ use warnings; use strict; use POSIX qw/strftime/; use Cwd qw/cwd realpath/; -use File::Basename qw/basename dirname/; +use File::Basename qw/fileparse/; our ($zone_dir, $git, $named_checkzone, $rndc, $class, $default_view, $update_record, $max_depth, $zones, $verbosity); our $user = getpwuid $<; t@@ -34,7 +34,7 @@ sub cleanup { unlink $lock_file } sub clean_exit { cleanup; exit shift } $SIG{__DIE__} = \&cleanup; -$_ = $cmd && +($_ = $cmd) && /^pre-receive$/ && pre_receive() || /^post-receive$/ && post_receive() || $update_record && /^update-record$/ && update_record($ARGV[2]); t@@ -61,21 +61,34 @@ sub git { # Load BIND config files specified in the $zones config variable. # First load the -default key, then the $user key. sub load_zones_config { - my $u = shift || '-default'; - - for my $f (keys %{$zones->{$u}}) { - next unless $f =~ m,^/, && -f $f; - open FILE, '<', $f or die $!; - while (<FILE>) { - if (/^\s*zone\s+"([^"]+)"/) { - $zones->{$user}->{$1} = $zones->{$u}->{$f}; + my $key = shift || '-default'; + + # move files not in a dir to a . dir for easier processing + for my $file (keys %{$zones->{$key}}) { + next if ref $zones->{$key}->{$file} eq 'HASH'; + $zones->{$key}->{'.'}->{$file} = $zones->{$key}->{$file}; + delete $zones->{$key}->{$file}; + } + + for my $dir (keys %{$zones->{$key}}) { + my $d = $zones->{$key}->{$dir}; + for my $file (keys %$d) { + $d->{$file} = $default_view if $d->{$file} eq 1; + $d->{$file} = [$d->{$file}] if ref $d->{$file} ne 'ARRAY'; + next unless $file =~ m,^/, && -f $file; + + open FILE, '<', $file or die $!; + while (<FILE>) { + if (/^\s*zone\s+"([^"]+)"/) { + $zones->{$user}->{$dir}->{$1} = $d->{$file}; + } } + close FILE; + delete $d->{$file} if $key ne '-default'; } - close FILE; - delete $zones->{$u}->{$f} if $u ne '-default'; } - load_zones_config($user) if $u eq '-default'; + load_zones_config($user) if $key eq '-default'; } sub process_files { t@@ -86,14 +99,14 @@ sub process_files { } sub process_file { - my $f = shift; # filename + my $file = shift; my (@newfile, $changed, @inc_by); - print ">> process_file($f)\n" if $verbosity >= 3; + print ">> process_file($file)\n" if $verbosity >= 3; - return 0 if $files{$f}; # already processed - return -1 unless -f $f; # deleted + return 0 if $files{$file}; # already processed + return -1 unless -f $file; # deleted - open FILE, '<', $f or die $!; + open FILE, '<', $file or die $!; my $n = 0; while (<FILE>) { $n++; t@@ -108,10 +121,10 @@ sub process_file { $changed = 1; } elsif (/^(\W*\$INCLUDE\W+)(\S+)(.*)$/) { # check $INCLUDE lines for files outside the user dir - my ($a,$file,$z) = ($1,$2,$3); - unless ($file =~ m,^$user/, && $file !~ /\.\./) { + my ($a,$inc_file,$z) = ($1,$2,$3); + unless ($inc_file =~ m,^$user/, && $inc_file !~ /\.\./) { close FILE; - die "Error in $f:$n: invalid included file name, it should start with: $user/\n"; + die "Error in $file:$n: invalid included file name, it should start with: $user/\n"; } } else { if ($n == 1 && /^;INCLUDED_BY\s+(.*)$/) { t@@ -127,29 +140,27 @@ sub process_file { close FILE; if ($changed) { - open FILE, '>', $f or die $!; + open FILE, '>', $file or die $!; print FILE for @newfile; close FILE; - my $fesc = $f; - $fesc =~ s/'/'\\''/g; - git "commit -m 'auto increment: $fesc' '$fesc'", 1; + git "commit -m 'auto increment: $file' '$file'", 1; } return 1; } sub find_inc_by { - my $f = shift; # filename - my $d = shift || 1; # recursion depth + my $file = shift; + my $depth = shift || 1; # recursion depth my @inc_by; - print ">> find_inc_by($f)\n" if $verbosity >= 3; + print ">> find_inc_by($file)\n" if $verbosity >= 3; - return 0 if $files{$f}; # already processed - return -1 unless -f $f; # deleted + return 0 if $files{$file}; # already processed + return -1 unless -f $file; # deleted $files{$_}++; - open FILE, '<', $f or die $!; + open FILE, '<', $file or die $!; if (<FILE> =~ /^;INCLUDED_BY\s+(.*)$/) { # add files listed after ;INCLUDED_BY to %files @inc_by = split /\s+/, $1; t@@ -159,8 +170,8 @@ sub find_inc_by { } close FILE; - if ($d++ < $max_depth) { - find_inc_by($_, $d) for @inc_by; + if ($depth++ < $max_depth) { + find_inc_by($_, $depth) for @inc_by; } else { print "Warning: ;INCLUDED_BY is followed only up to $max_depth levels,\n". " the following files are not reloaded: @inc_by\n"; t@@ -168,14 +179,15 @@ sub find_inc_by { } sub check_zones { - for my $f (keys %files) { + for my $file (keys %files) { # skip files with errors and those that are not in the config - next unless $files{$f} > 0 && $zones->{$user}->{$f}; - next if $f =~ /'/; - my $zone = basename $f; - print `$named_checkzone -kn -w .. '$zone' '$user/$f'`; + my ($zone, $dir) = fileparse $file; + $dir = substr $dir, 0, -1; + next unless $files{$file} > 0 && exists $zones->{$user}->{$dir}->{$zone}; + + print `$named_checkzone -kn -w .. '$zone' '$user/$file'`; clean_exit 1 if $?; # error, reject push - push @zones, $f; + push @zones, $file; } } t@@ -192,12 +204,11 @@ sub install_zones { git 'fetch'; git 'reset --hard remotes/origin/master'; - for my $f (@zones) { - my $zone = basename $f; - my $view = $zones->{$user}->{$f}; - $view = $default_view if $view eq 1; - $view = [$view] if ref $view ne 'ARRAY'; - `$rndc reload '$zone' $class $_` for @$view; + for my $file (@zones) { + my ($zone, $dir) = fileparse $file; + $dir = substr $dir, 0, -1; + my $view = $zones->{$user}->{$dir}->{$zone}; + print "$zone: ", `$rndc reload '$zone' $class $_` for @$view; } unlink $list_file; t@@ -221,7 +232,8 @@ sub pre_receive { # check what changed git "checkout -qf $new"; $_ = git "diff --raw $old..$new"; - $files{$1} = 0 while m,^:(?:[\w.]+\s+){5}([\w./-]+)$,gm; + # parse diff output, add only valid zone names to %files for parsing + $files{$1} = 0 while m,^:(?:[\w.]+\s+){5}([a-z0-9./-]+)$,gm; load_zones_config; process_files; t@@ -255,7 +267,7 @@ sub post_receive { } sub update_record { - my ($c, $f, @record) = split /\s+/, shift; + my ($c, $file, @record) = split /\s+/, shift; my ($ip) = $ENV{SSH_CLIENT} =~ /^([\d.]+|[a-f\d:]+)\s/i or die "Invalid IP address\n"; my $re = qr/^\s*/i; $re = qr/$re$_\s+/i for (@record); t@@ -266,7 +278,7 @@ sub update_record { chdir $user; git 'checkout -f master'; - open FILE, '<', $f or die "$f: $!"; + open FILE, '<', $file or die "$file: $!"; while (<FILE>) { my $line = $_; if (!$matched && s/($re)([\d.]+|[a-f\d:]+)/$1$ip/i) { t@@ -285,17 +297,15 @@ sub update_record { push @newfile, $line; } close FILE; - die "No matching record in $f: @record\n" unless $matched; + die "No matching record in $file: @record\n" unless $matched; - open FILE, '>', $f or die $!; + open FILE, '>', $file or die $!; print FILE for @newfile; close FILE; - my $fesc = $f; - $fesc =~ s/'/'\\''/g; - git "commit -m 'update-record: $fesc' '$fesc'", 1; + git "commit -m 'update-record: $file' '$file'", 1; - process_files $f; + process_files $file; # save new commits in a new branch git 'branch -D new'; DIR diff --git a/etc/gitzone.conf b/etc/gitzone.conf t@@ -6,8 +6,8 @@ # $user - name of the user gitzone is invoked by # directory where the zone files are copied to (no trailing slash) -# there should be one directory for each user here chowned to the users -$zone_dir = "/var/bind"; +# there should be one directory for each user here chowned to them +$zone_dir = '/var/bind'; # commands $git = '/usr/bin/git'; t@@ -31,9 +31,10 @@ $default_view = ''; # $zones defines which files in a user's repo can be loaded as zone files. # # You can define which view a zone belongs to, this can be -# - a string -# - an array with multiple views is allowed +# - a string for a single view +# - an array for multiple views # - or 1 to use the $default_view +# The view is used as a parameter for rndc reload. # # The basename of the files listed must be identical to the zone name. # If a file name starts with a / it's treated as a BIND config file t@@ -43,13 +44,18 @@ $default_view = ''; $zones = { # -default => { -# "/etc/bind/users/$user.conf" => 1, # allow every zone from this file, use the default view +# "/etc/bind/users/$user.conf" => 1, # allow every zone from this file, use the default view for them # }, -# user1 => { -# '/etc/bind/users/user1-local.conf' => 'local', # allow every zone from this file, use the local view -# 'example.com' => 1, # allow example.com, use the default view -# 'local/example.net' => 'local', # allow example.net, use the local view -# 'extern/example.net' => 'extern', # allow example.net, use the extern view -# 'common/example.net' => [qw(extern local)], # allow example.net, use both the local & extern view +# user1 => { # /etc/bind/users/user1.conf is loaded first and merged with the config below, as specified in -default above +# 'example.com' => 1, # allow example.com, use the default view for it +# 'example.net' => 'extern', # allow example.net, use the extern view for it +# 'example.org' => [qw(view1 view2)], # allow example.org, use both view1 & view2 for it +# local => { # local/ dir in the repo +# '/etc/bind/users/user1-local.conf' => 'local', # allow every zone from this file, use the local view for them +# 'example.net' => 'local', # allow example.net, use the local view for it +# }, +# 'foo/bar/baz' => { # foo/bar/baz/ dir in the repo +# 'example.org' => 1, # allow example.org, use the default view for it +# }, # }, }