moved more functions to the lib
authorHeiko Schlittermann (JUMPER) <hs@schlittermann.de>
Sat, 24 May 2014 23:01:17 +0200
changeset 15 aa1598910bb0
parent 14 0f37544f1b98
child 16 1cbe9dc60243
moved more functions to the lib and put there some code to save the new zone in case something happens.
bin/dnsvi
lib/DNS/Vi.pm
t/10-vidns.t
--- a/bin/dnsvi	Fri May 23 16:15:41 2014 +0200
+++ b/bin/dnsvi	Sat May 24 23:01:17 2014 +0200
@@ -1,39 +1,46 @@
 #! /usr/bin/perl
-#line 2
+#line 3
 use 5.010;
 use strict;
 use warnings;
 use if $ENV{DEBUG} // '' eq 'dnsvi' => 'Smart::Comments';
-use File::Temp;
 use Getopt::Long;
 use Pod::Usage;
 
 use blib;
 use DNS::Vi;
 
+sub slurp {
+    local $/ = undef;
+    local @ARGV = @_;
+    <>;
+}
+
 sub main {
     my %o = (
-	local  => undef,
+        local  => undef,
         key    => undef,
         server => undef,
         debug  => undef,
-	editor => $ENV{EDITOR}//'vi',
+        editor => $ENV{EDITOR} // 'vi',
+        skip => [qw/NS RRSIG NSEC3 NSEC3PARAM NSEC DNSKEY TSIG/],
     );
 
     GetOptions(
         'k|key=s'    => \$o{key},
         's|server=s' => \$o{server},
         'd|debug!'   => \$o{debug},
-	'l|local!'   => \$o{local},
-	'editor=s'   => \$o{editor},
+        'l|local!'   => \$o{local},
+        'editor=s'   => \$o{editor},
       )
-      && @ARGV == 1
+      && @ARGV >= 1
       or pod2usage();
 
     my $zone = shift @ARGV;
 
-    $o{server} = $o{local} ? 'localhost' : (split ' ', `dig +short soa $zone`)[0]
-	if not defined $o{server};
+    $o{server} =
+      $o{local} ? 'localhost' : (split ' ', `dig +short soa $zone`)[0]
+      if not defined $o{server};
 
     my @dig = (
         dig => 'AXFR',
@@ -42,59 +49,38 @@
         $zone
     );
 
-    my @zone1 = grep {
-        not $_->{rrset}{rrtype} ~~
-          [qw(RRSIG NSEC3 NSEC3PARAM NSEC DNSKEY TSIG)]
-    } parse($_ = `@dig`) or die $_;
+    my @zone1 = parse($_ = `@dig`, { -skip => $o{skip} } )
+      or die "Empty zone\n";
+    my @zone2 = do {
+	if (my $file = shift @ARGV) {
+	    parse(slurp($file), { -skip => $o{skip} });
+	} 
+	else {
+	    edit(@zone1, { -skip => $o{skip}, -editor => $o{editor} });
+	}
+    };
+    ### @zone2
 
-    my $tmp = File::Temp->new();
-    $tmp->print(nice @zone1);
-    $tmp->flush();
-    system $o{editor} => $tmp->filename;
-    $tmp->seek(0, 0);
-    my @zone2 = parse(<$tmp>);
     my ($add, $del) = delta(\@zone1, \@zone2);
-
     if ((@$add + @$del) == 0) {
-	say 'Nothing changed';
-	return 0;
+	    say 'nothing changed';
+	    return 0;
     }
 
-    my $orig_soa =
-      (grep { $_->{rrtype} eq 'SOA' } map { $_->{rrset} } @zone1)[0];
-
-    my @cmds = (
-	$o{local} ? () : "server $o{server}",
-        "prereq yxrrset @{$orig_soa}{qw{label rrtype data}}",
-        (map { "update delete $_" } @$del),
-        (map { "update add $_" } @$add),
-	'show',
-	'send',
-	'answer',
-    );
-
-    print <<_EOF, join "\n" => @cmds, '', '';
-# The following commands are about to be sent via nsupdate
-# to the master server:
-
-_EOF
-    print '# Please confirm (yes/NO): ';
+    say 'The following changes need your confirmation.';
+    say join "\n", show($add, $del);
+    print 'confirm (yes|NO): ';
     return 1 if <STDIN> !~ /^y/i;
 
-    my @nsupdate = (
-        'nsupdate',
-        defined $o{debug} ? ('-d') : (),
-        defined $o{key} ? (-k => $o{key}) : (),
-	defined $o{local} ? ('-l') : (),
-    );
-
-    open(my $nsupdate, '|-') or do {
-        exec @nsupdate;
-        die "Can't exec @nsupdate: $!\n";
+    update(\@zone1, $add, $del, {
+	-server => $o{server},
+	-local => $o{local},
+	-debug => $o{debug},
+	-key => $o{key}})
+    or do { 
+	save(\@zone2, ",dnsvi-$$")
+	and say "Saved as ',dnsvi-$$'";
     };
-    say $nsupdate join "\n", @cmds;
-    close($nsupdate);
-    say "nsupdate returned $?";
 
     return 0;
 }
@@ -109,7 +95,7 @@
 
 =head1 SYNOPSIS
 
- vidns [-l] [-k key] [-s server] [-d] <zone>
+ vidns [-l] [-k key] [-s server] [-d] <zone> [<file>]
 
 =head1 DESCRIPTION
 
--- a/lib/DNS/Vi.pm	Fri May 23 16:15:41 2014 +0200
+++ b/lib/DNS/Vi.pm	Sat May 24 23:01:17 2014 +0200
@@ -4,14 +4,16 @@
 use warnings;
 use if $ENV{DEBUG}//''  eq 'dnsvi' => 'Smart::Comments';
 use Digest::SHA qw(sha512_hex);
+use File::Temp;
 
 use base 'Exporter';
 
-our @EXPORT = qw(ttl2h h2ttl parse delta nice);
+our @EXPORT = qw(ttl2h h2ttl parse delta nice edit update show save);
 our @EXPORT_OK = ();
 
 sub parse {
-    my $data = join '', @_;
+    my %arg =  %{pop @_} if ref $_[-1] eq 'HASH';
+    my $data = shift;
     my @lines = split /\n/, $data;
 
     my @zone;
@@ -42,6 +44,7 @@
                       rrtype => uc $+{rrtype},
                       data   => $+{data},
                 );
+		next if $rrset{rrtype} ~~ $arg{-skip};
 
                 if ($rrset{rrtype} eq 'SOA') {
                     next if $soa_seen;
@@ -117,8 +120,9 @@
     return $out // $ttl;
 }
 
+{
+    my %order = map { state $n = 0; $_ => ++$n } qw(SOA NS TXT MX A AAAA);
 sub nice {
-    my %order = map { state $n = 0; $_ => ++$n } qw(SOA NS TXT MX A AAAA);
 
     # get a list of { id => $id, rrset => \%rrset }
     my @zone =
@@ -155,9 +159,9 @@
           $r{data};
     };
     push @out, $print->($_) foreach @zone;
-    return join "\n", @out;
+    return join "\n", @out, '';
 }
-
+}
 sub delta {
     my ($zone1, $zone2) = @_;
     my %zone1 = map { $_->{id}, $_->{rrset} } @$zone1;
@@ -174,4 +178,66 @@
     return (\@add, \@del);
 }
 
+sub edit {
+    my %arg = %{pop @_} if ref $_[-1] eq 'HASH';
+    my @zone = @_;
+
+    my $tmp = File::Temp->new();
+    $tmp->print(nice @zone);
+    $tmp->flush();
+    system $arg{-editor} => $tmp->filename;
+    $tmp->seek(0, 0);
+    return parse(do { local $/ = undef; <$tmp>}, {-skip => $arg{-skip}});
+}
+
+sub show {
+    my ($add, $del) = @_;
+    my @out = (
+	(map { " - $_ " } @$del),
+	(map { " + $_ " } @$add),
+    );
+    return @out;
+}
+
+sub update {
+    my %arg = %{pop @_} if ref $_[-1] eq 'HASH';
+    my ($zone1, $add, $del) = @_;
+
+    my $orig_soa =
+      (grep { $_->{rrtype} eq 'SOA' } map { $_->{rrset} } @$zone1)[0];
+
+    my @cmds = (
+        $arg{-local} ? () : "server $arg{-server}",
+        "prereq yxrrset @{$orig_soa}{qw{label rrtype data}}",
+        (map { "update delete $_" } @$del),
+        (map { "update add $_" } @$add),
+        'show',
+        'send',
+        'answer',
+    );
+    my @nsupdate = (
+        'nsupdate',
+        defined $arg{-debug} ? ('-d') : (),
+        defined $arg{-key} ? (-k => $arg{-key}) : (),
+        defined $arg{-local} ? ('-l') : (),
+    );
+
+    open(my $nsupdate, '|-') or do {
+        exec @nsupdate;
+        die "Can't exec @nsupdate: $!\n";
+    };
+    say $nsupdate join "\n", @cmds;
+    close($nsupdate);
+    say "nsupdate returned $?";
+    return $? ? undef : 1;
+}
+
+sub save {
+    my ($zone, $file) = @_;
+    open(my $fh, '>', $file) or die "Can't open >$file: $!\n";
+    print $fh nice @$zone;
+    close($fh);
+    
+}
+
 1;
--- a/t/10-vidns.t	Fri May 23 16:15:41 2014 +0200
+++ b/t/10-vidns.t	Sat May 24 23:01:17 2014 +0200
@@ -5,37 +5,43 @@
 use warnings;
 
 use_ok 'DNS::Vi' or BAIL_OUT 'DNS::Vi not found!';
-can_ok 'DNS::Vi', qw(ttl2h h2ttl parse nice delta);
+can_ok 'DNS::Vi', qw(ttl2h h2ttl parse nice delta edit update show);
 
 # TODO: more tests!
 is ttl2h(86400), '1d',  '-> 1d';
 is h2ttl('1d'),  86400, '<- 1d';
 
-my $data = do {
+my $data1 = do {
     local $/    = undef;
     local @ARGV = 't/samples/kugelbus-axfr';
     <>;
 };
 
-my @zone1 = parse($data);
-is @zone1, 64 => '64 rrsets';
+# check parser with and without skip list
+is parse($data1), 64 => '64 rrsets';
+my @zone1 =
+  parse($data1, { -skip => [qw(RRSIG NSEC3 NSEC3PARAM NSEC DNSKEY TSIG)] });
+is @zone1, 18 => '18 rrsets';
 
-$data =~ s{^kugelbus.*?IN\s+MX.*?$}{}m;
-$data .= <<_;
-foo 1h	txt "foo"
-    30m	A   1.1.1.1
-_
-my @zone2 = parse($data);
-is @zone2, 65, => '65 rrsets';
+subtest 'parsed data' => sub {
+    my ($soa) =
+      map { $_->{rrset} } grep { $_->{rrset}{rrtype} eq 'SOA' } @zone1;
+    is ref $soa, 'HASH' => 'got result hash';
+    is $soa->{rrtype}, 'SOA' => 'is SOA';
+    is $soa->{data},
+        'pu.schlittermann.de. '
+      . 'hostmaster.net.schlittermann.de. '
+      . '18 86400 7200 604800 86400' => 'has expected data';
 
-my ($add, $del) = delta(\@zone1, \@zone2);
-is @$add, 2 => '2 sets to add';
-is @$del, 1 => '1 set to delete';
+    my @txt = map { $_->{rrset} } grep { $_->{rrset}{rrtype} eq 'TXT' } @zone1;
+    is @txt, 3 => 'got 3 txt records';
+};
 
-is_deeply $add,
-  ['foo.kugelbus.de. 1800 A 1.1.1.1', 'foo.kugelbus.de. 3600 TXT "foo"'] =>
-  'added datasets';
-is_deeply $del,
-  ['kugelbus.de. 86400 MX 10 ssl.schlittermann.de.'] => 'removed datasets';
+# delta should find noting
+subtest 'delta' => sub { 
+    my ($add, $del) = delta(\@zone1, \@zone1);
+    is @$add, 0 => 'nothing added';
+    is @$del, 0 => 'nothing deleted';
+};
 
 done_testing();