1use strict;
2use warnings;
3
4package Net::Amazon::Route53::HostedZone;
5{
6  $Net::Amazon::Route53::HostedZone::VERSION = '0.122310';
7}
8use Mouse;
9use HTML::Entities;
10
11use Net::Amazon::Route53::Change;
12use Net::Amazon::Route53::ResourceRecordSet;
13
14=head2 SYNOPSIS
15
16    my $hostedzone = Net::Amazon::Route53::HostedZone->new(...);
17    # use methods on $hostedzone
18
19=cut
20
21=head2 ATTRIBUTES
22
23=cut
24
25=head3 route53
26
27A L<Net::Amazon::Route53> object, needed and used to perform requests
28to Amazon's Route 53 service
29
30=cut
31
32has 'route53' => (is => 'rw', isa => 'Net::Amazon::Route53', required => 1,);
33
34=head3 id
35
36The hosted zone's id
37
38=head3 name
39
40The hosted zone's name; ends in a dot, i.e.
41
42    example.com.
43
44=head3 callerreference
45
46The CallerReference attribute for the hosted zone
47
48=head3 comment
49
50Any Comment given when the zone is created
51
52=cut
53
54has 'id'   => (is => 'rw', isa => 'Str', required => 1, default => '');
55has 'name' => (is => 'rw', isa => 'Str', required => 1, default => '');
56has 'callerreference' =>
57    (is => 'rw', isa => 'Str', required => 1, default => '');
58has 'comment' => (is => 'rw', isa => 'Str', required => 1, default => '');
59
60=head3 nameservers
61
62Lazily loaded, returns a list of the nameservers authoritative for this zone
63
64=cut
65
66has 'nameservers' => (
67    is      => 'rw',
68    isa     => 'ArrayRef[Str]',
69    lazy    => 1,
70    default => sub {
71        my $self = shift;
72        my $resp = $self->route53->request('get',
73            'https://route53.amazonaws.com/2010-10-01/' . $self->id);
74        my @nameservers =
75            map {decode_entities($_)}
76            @{ $resp->{DelegationSet}{NameServers}{NameServer} };
77        \@nameservers;
78    });
79
80=head3 resource_record_sets
81
82Lazily loaded, returns a list of the resource record sets
83(L<Net::Amazon::Route53::ResourceRecordSet> objects) for this zone.
84
85=cut
86
87has 'resource_record_sets' => (
88    is      => 'rw',
89    isa     => 'ArrayRef',
90    lazy    => 1,
91    default => sub {
92        my $self             = shift;
93        my $next_record_name = '';
94        my @resource_record_sets;
95        while (1) {
96            my $resp = $self->route53->request('get',
97                      'https://route53.amazonaws.com/2010-10-01/'
98                    . $self->id
99                    . '/rrset?maxitems=100'
100                    . $next_record_name);
101            my $set = $resp->{ResourceRecordSets}{ResourceRecordSet};
102            my @results = ref($set) eq 'ARRAY' ? @$set : ($set);
103            for my $res (@results) {
104                push @resource_record_sets,
105                    Net::Amazon::Route53::ResourceRecordSet->new(
106                    route53    => $self->route53,
107                    hostedzone => $self,
108                    name       => decode_entities($res->{Name}),
109                    ttl        => $res->{TTL} || 0,
110                    type       => decode_entities($res->{Type}),
111                    values     => [
112                        map {decode_entities($_->{Value})} @{
113                            ref $res->{ResourceRecords}{ResourceRecord} eq
114                                'ARRAY'
115                            ? $res->{ResourceRecords}{ResourceRecord}
116                            : [ $res->{ResourceRecords}{ResourceRecord} ] }
117                    ],
118                    );
119            }
120            last unless $resp->{NextRecordName};
121            $next_record_name = '&name=' . $resp->{NextRecordName};
122        }
123        \@resource_record_sets;
124    });
125
126=head2 METHODS
127
128=cut
129
130=head3 create
131
132Creates a new zone. Needs all the attributes (name, callerreference and comment).
133
134Takes an optional boolean parameter, C<wait>, to indicate whether the request should
135return straightaway (default, or when C<wait> is C<0>) or it should wait until the
136request is C<INSYNC> according to the Change's status.
137
138Returns a L<Net::Amazon::Route53::Change> object representing the change requested.
139
140=cut
141
142sub create {
143    my $self = shift;
144    my $wait = shift;
145    $wait = 0 if !defined $wait;
146    $self->name =~ /\.$/
147        or die "Zone name needs to end in a dot, to be created\n";
148    my $request_xml_str = <<'ENDXML';
149<?xml version="1.0" encoding="UTF-8"?>
150<CreateHostedZoneRequest xmlns="https://route53.amazonaws.com/doc/2010-10-01/">
151    <Name>%s</Name>
152    <CallerReference>%s</CallerReference>
153    <HostedZoneConfig>
154        <Comment>%s</Comment>
155    </HostedZoneConfig>
156</CreateHostedZoneRequest>
157ENDXML
158    my $request_xml = sprintf($request_xml_str,
159        map {$_} $self->name,
160        $self->callerreference, $self->comment);
161    my $resp = $self->route53->request(
162        'post',
163        'https://route53.amazonaws.com/2010-10-01/hostedzone',
164        'content-type' => 'text/xml; charset=UTF-8',
165        Content        => $request_xml,
166    );
167    $self->id($resp->{HostedZone}{Id});
168    my $change = Net::Amazon::Route53::Change->new(
169        route53 => $self->route53,
170        (
171            map {lc($_) => decode_entities($resp->{ChangeInfo}{$_})}
172                qw/Id Status SubmittedAt/
173        ),
174    );
175    $change->refresh();
176    return $change if !$wait;
177
178    while (lc($change->status) ne 'insync') {
179        sleep 2;
180        $change->refresh();
181    }
182    return $change;
183}
184
185=head3 delete
186
187Deletes the zone. A zone can only be deleted by Amazon's Route 53 service if it
188contains no records other than a SOA or NS.
189
190Takes an optional boolean parameter, C<wait>, to indicate whether the request should
191return straightaway (default, or when C<wait> is C<0>) or it should wait until the
192request is C<INSYNC> according to the Change's status.
193
194Returns a L<Net::Amazon::Route53::Change> object representing the change requested.
195
196=cut
197
198sub delete {
199    my $self = shift;
200    my $wait = shift;
201    $wait = 0 if !defined $wait;
202    my $resp =
203        $self->route53->request('delete',
204        'https://route53.amazonaws.com/2010-10-01/' . $self->id,
205        );
206    my $change = Net::Amazon::Route53::Change->new(
207        route53 => $self->route53,
208        (
209            map {lc($_) => decode_entities($resp->{ChangeInfo}{$_})}
210                qw/Id Status SubmittedAt/
211        ),
212    );
213    $change->refresh();
214    return $change if !$wait;
215    while (lc($change->status) ne 'insync') {
216        sleep 2;
217        $change->refresh();
218    }
219    return $change;
220}
221
222no Mouse;
223
224=head1 AUTHOR
225
226Marco FONTANI <mfontani@cpan.org>
227
228=head1 COPYRIGHT AND LICENSE
229
230This software is copyright (c) 2011 by Marco FONTANI.
231
232This is free software; you can redistribute it and/or modify it under
233the same terms as the Perl 5 programming language system itself.
234
235=cut
236
2371;
238