File Coverage

blib/lib/Net/DNS/Create/Route53.pm
Criterion Covered Total %
statement 41 78 52.5
branch 17 38 44.7
condition 6 24 25.0
subroutine 10 15 66.6
pod 0 8 0.0
total 74 163 45.4


line stmt bran cond sub pod time code
1             # Copyright (c) 2013 David Caldwell, All Rights Reserved.
2              
3             package Net::DNS::Create::Route53;
4 1     1   7 use feature ':5.10';
  1         1  
  1         143  
5 1     1   6 use strict;
  1         2  
  1         31  
6 1     1   5 use warnings;
  1         9  
  1         38  
7              
8 1     1   6 use Net::DNS::Create qw(internal full_host email interval);
  1         2  
  1         9  
9 1     1   1014 use Net::Amazon::Route53;
  1         141245  
  1         1944  
10              
11             our %config;
12             sub import {
13 1     1   2 my $package = shift;
14 1         6 my %c = @_;
15 1         8 $config{$_} = $c{$_} for keys %c;
16             }
17              
18             my $r53;
19             sub r53() {
20 0   0 0 0 0 $r53 //= Net::Amazon::Route53->new(id => $config{amazon_id},
21             key => $config{amazon_key});
22             }
23              
24             my $zones;
25             sub hosted_zone($) {
26             # The eval works around a bug in Net::Amazon::Route53 where it dies if there are no zones at all.
27 0 0 0 0 0 0 $zones = eval { [r53->get_hosted_zones()] } || [] unless defined $zones;
28 0   0     0 (grep { $_->name eq $_[0] } @$zones)[0] // undef;
  0         0  
29             }
30              
31             sub txt(@) {
32 5     5 0 185 map { "\"$_\"" } @_;
  6         77  
33             }
34              
35             sub group_by_type_and_name($$) {
36 1     1 0 2 my ($re, $entries) = @_;
37 1         2 my %set;
38 1         3 for my $r (grep { lc($_->type) =~ $re } @$entries) {
  26         293  
39 10         274 push @{$set{$r->type .'_'. $r->name}}, $r;
  10         64  
40             }
41 1         50 map { $set{$_} } keys %set;
  7         25  
42             }
43              
44             my @domain;
45 1     1   48 sub _domain() { @domain } # Hook for testing
46             sub domain($$) {
47 1     1 0 4 my ($package, $domain, $entries) = @_;
48              
49 1         3 my @entries = map { ;
50 26         795 my $rr = lc $_->type;
51              
52 26 50 33     786 $rr eq 'soa' ? () : # Amazon manages its own SOA stuff. Just ignore things we might have.
    100 100        
    100          
    100          
    100          
53             $rr eq 'rp' ? (warn("Amazon doesn't support RP records :-(") && ()) :
54              
55             $rr eq 'mx' || $rr eq 'ns' || $rr eq 'srv' || $rr eq 'txt' ? () : # Handled specially, below
56              
57             +{
58             action => 'create',
59             name => $_->name.'.',
60             ttl => $_->ttl,
61             type => uc $rr,
62             $rr eq 'a' ? (value => $_->address) :
63             $rr eq 'cname' ? (value => $_->cname.'.') :
64             (err => warn "Don't know how to handle \"$rr\" RRs yet.")
65              
66             }
67             } @$entries;
68              
69             # Amazon wants all NS,MX,TXT and SRV entries for a particular name in one of their entries. We get them in as
70             # separate entries so first we have to group them together.
71 1         54 push @entries, map { my @set = @$_;
  7         225  
72 7         31 my $rr = lc $set[0]->type;
73 2         40 $rr eq 'ns' && $set[0]->name.'.' eq $domain ? () : # Amazon manages its own NS stuff. Just ignore things we might have.
74             +{
75             action => 'create',
76             name => $set[0]->name.'.',
77             ttl => $set[0]->ttl,
78             type => uc $rr,
79 0         0 $rr eq 'mx' ? (records => [map { $_->preference." ".$_->exchange.'.' } @set]) :
80 1         12679 $rr eq 'ns' ? (records => [map { $_->nsdname.'.' } @set] ) :
81 5         102 $rr eq 'srv' ? (records => [map { $_->priority ." ".$_->weight ." ".$_->port ." ".$_->target.'.' } @set]) :
82 7 50 66     141 $rr eq 'txt' ? (records => [map { join ' ', txt($_->char_str_list) } @set]) :
    100          
    50          
    100          
    100          
83             (err => die uc($rr)." can't happen here!")
84             }
85             } group_by_type_and_name(qr/^(?:mx|ns|srv|txt)$/, $entries);
86              
87 1         104 push @domain, { name => $domain,
88             entries => \@entries };
89             }
90              
91             my $counter = rand(1000);
92             sub master() {
93 0     0 0   my ($package) = @_;
94 0           local $|=1;
95              
96 0           for my $domain (@domain) {
97 0           my $zone = hosted_zone(full_host($domain->{name}));
98 0 0 0       if (!$zone && scalar @{$domain->{entries}}) {
  0            
99 0           my $hostedzone = Net::Amazon::Route53::HostedZone->new(route53 => r53,
100             name => $domain->{name},
101             comment=>(getpwuid($<))[0].'/'.__PACKAGE__,
102             callerreference=>__PACKAGE__."-".localtime."-".($counter++));
103 0           print "New Zone: $domain->{name}...";
104 0           $hostedzone->create();
105 0           $zone = $hostedzone;
106 0           print "Created. Nameservers:\n".join('', map { " $_\n" } @{$zone->nameservers});
  0            
  0            
107             }
108              
109 0 0         if ($zone) {
110 0 0 0       my $current = [ grep { $_->type ne 'SOA' && ($_->type ne 'NS' || $_->name ne $domain->{name}) } @{$zone->resource_record_sets} ];
  0            
  0            
111 0           my $new = [ map { Net::Amazon::Route53::ResourceRecordSet->new(%{$_},
  0            
  0            
112 0   0       values => [$_->{value} // @{$_->{records}}],
113             route53 => r53,
114 0           hostedzone => $zone) } @{$domain->{entries}} ];
115 0           printf "%s: %d -> %d\n", $domain->{name}, scalar @$current, scalar @$new;
116 0 0         my $change = scalar @$current > 0 ? r53->atomic_update($current,$new) :
    0          
117             scalar @$new > 0 ? r53->batch_create($new) :
118             undef;
119              
120 0 0         unless (scalar @{$domain->{entries}}) {
  0            
121 0           print "Deleting $domain->{name}\n";
122 0           $zone->delete;
123             }
124             }
125             }
126             }
127              
128             sub domain_list($@) {
129 0     0 0   my $zone = hosted_zone(full_host($_[0]));
130 0 0         printf "%-30s %-30s %s\n", $zone ? $zone->id : '', $_[0], !$zone ? '' : ' ['.join(" ",@{$zone->nameservers}).']';
  0 0          
131             }
132              
133 0     0 0   sub master_list($$) {
134             # This doesn't really make sense in the route53 context
135             }
136              
137             1;
138             __END__