moved more functions to the lib
and put there some code to save the new zone in case something happens.
--- 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();