File Coverage

blib/lib/Net/Dynect/REST/ResourceRecord.pm
Criterion Covered Total %
statement 15 178 8.4
branch 0 96 0.0
condition 0 35 0.0
subroutine 5 19 26.3
pod 10 12 83.3
total 30 340 8.8


line stmt bran cond sub pod time code
1             package Net::Dynect::REST::ResourceRecord;
2             # $Id: ResourceRecord.pm 172 2010-09-27 06:26:59Z james $
3 1     1   6 use strict;
  1         2  
  1         40  
4 1     1   6 use warnings;
  1         2  
  1         63  
5 1     1   10 use overload '""' => \&_as_string;
  1         3  
  1         12  
6 1     1   75 use Carp;
  1         2  
  1         90  
7 1     1   7 use Net::Dynect::REST::RData;
  1         2  
  1         2315  
8             our $VERSION = do { my @r = (q$Revision: 172 $ =~ /\d+/g); sprintf "%d."."%03d" x $#r, @r };
9              
10             =head1 NAME
11              
12             Net::Dynect::REST::ResourceRecord - An abstract DNS resource record object
13              
14             =head1 SYNOPSIS
15              
16             use Net::Dynect::REST:ARecord;
17             my $record = Net::Dynect::REST:ARecord->new(connection => $dynect);
18             $record->get('example.com', 'www.example.com');
19             $ttl = $record->ttl;
20              
21             =head1 METHODS
22              
23             =head2 Creating
24              
25             =over 4
26              
27             =item Net::Dynect::REST:ARecord->new()
28              
29             This constructor takes arguments of the connection object (Net::Dynect::REST), and optionally a zone and arecord FQDN to fetch.
30              
31              
32             =cut
33              
34             sub new {
35 0     0 1   my $proto = shift;
36 0   0       my $self = bless {}, ref($proto) || $proto;
37 0           my %args = @_;
38 0 0         $self->{connection} = $args{connection} if defined $args{connection};
39             # If we have a record_id, then we should load this.
40 0 0 0       if ( defined( $args{fqdn} )
      0        
41             && defined( $args{zone} )
42             && defined( $args{record_id} ) )
43             {
44 0 0         $self->get(
45             zone => $args{zone},
46             fqdn => $args{fqdn},
47             record_id => $args{record_id}
48             ) && return $self;
49 0           return;
50             }
51 0 0         $self->zone( $args{zone} ) if ( defined $args{zone} );
52 0 0         $self->fqdn( $args{fqdn} ) if ( defined $args{fqdn} );
53 0 0         $self->rdata( $args{rdata} ) if ( defined $args{rdata} );
54 0 0         $self->ttl( $args{ttl} ) if ( defined $args{ttl} );
55 0           return $self;
56             }
57              
58             =back
59             =item Net::Dynect::REST:ARecord-find(connection => $dynect, zone => $zone, fqdn => $fqdn);
60              
61             This will return an array of objects that match the Name and Zone. Each A Record may have multiple entries in the zone.
62              
63             =cut
64              
65             sub find {
66 0     0 0   my $proto = shift;
67 0           my %args = @_;
68 0 0 0       if (
69             not( defined( $args{connection} )
70             && ref( $args{connection} ) eq "Net::Dynect::REST" )
71             )
72             {
73 0           carp "Need a connection (Net::Dynect::REST)";
74 0           return;
75             }
76 0 0         if ( not( defined $args{zone} ) ) {
77 0           carp "Need a zone to look in";
78 0           return;
79             }
80 0 0         if ( not defined $args{fqdn} ) {
81 0           carp "Need a fully qualified domain name (FQDN) to look for";
82 0           return;
83             }
84 0           my $request = Net::Dynect::REST::Request->new(
85             operation => 'read',
86             service => sprintf( "%s/%s/%s", __PACKAGE__->_service_base_uri, $args{zone}, $args{fqdn} )
87             );
88 0 0         if ( not $request ) {
89 0           carp "Request not valid: $request";
90 0           return;
91             }
92              
93 0           my $response = $args{connection}->execute($request);
94             # Not keeping track fo this response as we're not an object!
95              
96 0 0         if ( not $response ) {
97 0           carp "Response not valid: $response";
98 0           return;
99             }
100              
101 0 0         if ( $response->status !~ /^success$/i ) {
102 0           carp $response->status;
103 0           return;
104             }
105              
106 0 0         if ( ref( $response->data ) ne "ARRAY" ) {
107             # Didn't get a list of records back, probably becuase it doesn't exist!
108 0           return;
109             }
110              
111 0           my @records;
112 0           foreach ( @{ $response->data } ) {
  0            
113 0 0         if ( $_->value =~ m!/REST/([^\/]+)/([^\/]+)/([^\/]+)/(\d+)$! ) {
114 0           eval "require Net::Dynect::REST::$1";
115 0           push @records,
116             "Net::Dynect::REST::$1"->new(
117             connection => $args{connection},
118             fqdn => $3,
119             zone => $2,
120             record_id => $4
121             );
122             }
123             else {
124 0           carp "Could not understand " . $_->data;
125 0           return;
126             }
127             }
128 0           return @records;
129             }
130              
131             =head2 Operations
132              
133             =over 4
134              
135             =item $record->get( $zone, $fqdn [, $redord_id] )
136              
137             This will attempt to load the data from Dynect for the given fully qualified domain name, in the given zone.
138              
139             =cut
140              
141             sub get {
142 0     0 1   my $self = shift;
143 0           my %args = @_;
144              
145 0 0 0       if ( not( defined( $args{zone} ) || $self->zone ) ) {
146 0           carp "Zone needs to be set";
147 0           return;
148             }
149              
150 0 0 0       if ( not( defined( $args{fqdn} ) || $self->fqdn ) ) {
151 0           carp "FQDN needs to be set";
152 0           return;
153             }
154              
155 0   0       my $request = Net::Dynect::REST::Request->new(
      0        
      0        
156             operation => 'read',
157             service => sprintf(
158             "%s/%s/%s/%s",
159             $self->_service_base_uri,
160             $args{zone} || $self->zone,
161             $args{fqdn} || $self->fqdn,
162             $args{record_id} || $self->record_id
163             )
164             );
165              
166 0 0         if ( not $request ) {
167 0           carp "Invalid request: $request";
168 0           return;
169             }
170              
171 0           my $response = $self->{connection}->execute($request);
172 0           $self->last_response($response);
173              
174 0 0         if ( not $response ) {
175 0           carp "Invalid response: $response";
176 0           return;
177             }
178              
179 0 0         if ( $response->status !~ /^success$/i ) {
180 0           carp $response->status;
181 0           return;
182             }
183              
184 0           $self->fqdn( $response->data->fqdn );
185 0           $self->record_id( $response->data->record_id );
186 0           $self->zone( $response->data->zone );
187 0           $self->rdata(
188             Net::Dynect::REST::RData->new( data => $response->data->rdata ) );
189 0           $self->record_type( $response->data->record_type );
190 0           $self->ttl( $response->data->ttl );
191 0           return 1;
192             }
193              
194             =item $arecord->save();
195              
196             This will create a new ARecord resource.
197             You need to already populate the B, B, and B attributes with the correct data. The B should be a Net::Dynect::REST::RData object, with the B
field set to one IPv4 address, such as:
198              
199             Net::Dynect::REST::RData->new(data => {address => '1.2.3.4'});
200              
201             =cut
202              
203             sub save {
204 0     0 1   my $self = shift;
205 0           my %args = @_;
206              
207 0 0         if ( not defined $self->{connection} ) {
    0          
    0          
208 0           carp "Don't have a connection";
209 0           return;
210             }
211             elsif ( not defined $self->fqdn ) {
212 0           carp "Don't have an FQDN for this record";
213 0           return;
214             }
215             elsif ( not defined $self->rdata ) {
216 0           carp "Need an rdata structure with the address";
217 0           return;
218             }
219              
220 0   0       my $request = Net::Dynect::REST::Request->new(
221             operation => 'create',
222             service => $self->_service_base_uri ."/" . $self->zone . "/" . $self->fqdn,
223             params => { rdata => $self->rdata->rdata, ttl => $args{ttl} || 0 }
224             );
225              
226 0           my $response = $self->{connection}->execute($request);
227 0           $self->last_response($response);
228 0 0         if ( $response->status !~ /^success$/i ) {
229 0           carp "Response failed: $response";
230 0           return;
231             }
232             #print $response . "\n";
233 0           return 1;
234             }
235              
236             sub delete {
237 0     0 0   my $self = shift;
238 0 0         return unless defined $self->{connection};
239 0 0         return unless defined $self->zone;
240 0 0 0       return unless defined $self->fqdn && $self->record_id;
241 0           my $request = Net::Dynect::REST::Request->new(
242             operation => 'delete',
243             service => __PACKAGE__->_service_base_uri . '/' . $self->zone . '/' . $self->fqdn . '/' . $self->record_id
244             );
245 0           my $response = $self->{connection}->execute($request);
246 0           $self->last_response($response);
247 0 0         if ( $response->status =~ /^success$/i ) {
248 0           $self->{fqdn} = undef;
249 0           $self->{record_id} = undef;
250 0           $self = undef;
251 0           return 1;
252             }
253             else {
254 0 0         printf "%s\n", $response->msgs->[0]->info if defined $response->msgs;
255 0           return 0;
256             }
257             }
258              
259             =back
260              
261             =head2 Attributes
262              
263             =over 4
264              
265             =item fqdn
266              
267             This is the Fully Qaulified Domain Name of the A Record.
268              
269             =cut
270              
271             sub fqdn {
272 0     0 1   my $self = shift;
273 0 0         if (@_) {
274 0           my $new = shift;
275 0 0 0       if ( defined $self->{fqdn} && $self->{fqdn} ne $new ) {
    0          
276 0           carp
277             "Cannot change name from what it has been set to. Create a new instance for a new record, and delete the old one.";
278 0           return;
279             }
280             elsif ( $new !~ /^\S+/ ) {
281 0           carp "FQDN names must not have spaces in them: '$new'";
282 0           return;
283             }
284 0           $self->{fqdn} = $new;
285             }
286 0           return $self->{fqdn};
287             }
288              
289             =item zone
290              
291             the is the DNS zone the record lives in.
292              
293             =cut
294              
295             sub zone {
296 0     0 1   my $self = shift;
297 0 0         if (@_) {
298 0           my $new = shift;
299 0 0         if ( defined $self->{zone} ) {
    0          
300 0           carp
301             "Cannot change name from what it has been set to. Create a new instance for a new record, and delete the old one.";
302 0           return;
303             }
304             elsif ( $new !~ /^\S+/ ) {
305 0           carp "Zone names must not have spaces in them: '$new'";
306 0           return;
307             }
308 0           $self->{zone} = $new;
309             }
310 0           return $self->{zone};
311             }
312              
313             =item rdata
314              
315             This is the address record data
316              
317             =cut
318              
319             sub rdata {
320 0     0 1   my $self = shift;
321 0 0         if (@_) {
322 0           my $new = shift;
323 0           $self->{rdata} = $new;
324             }
325 0           return $self->{rdata};
326             }
327              
328             =item record_type
329              
330             This is the record type.
331              
332             =cut
333              
334             sub record_type {
335 0     0 1   my $self = shift;
336 0 0         if (@_) {
337 0           my $new = shift;
338 0           $self->{record_type} = $new;
339             }
340 0           return $self->{record_type};
341             }
342              
343             =item record_id
344              
345             This is unique to each record.
346              
347             =cut
348              
349             sub record_id {
350 0     0 1   my $self = shift;
351 0 0         if (@_) {
352 0           my $new = shift;
353 0 0         if ( $new !~ /^\d*$/ ) {
354 0           carp "Invalid record id: $new";
355 0           return;
356             }
357 0           $self->{record_id} = $new;
358             }
359 0           return $self->{record_id};
360             }
361              
362             =item ttl
363              
364             This is the time to live for the reord. Use 0 to inherit the zone default.
365              
366             =cut
367              
368             sub ttl {
369 0     0 1   my $self = shift;
370 0 0         if (@_) {
371 0           my $new = shift;
372 0 0         if ( $new !~ /^\d+$/ ) {
373 0           carp "New TTL should be numeric";
374 0           return;
375             }
376 0           $self->{ttl} = $new;
377             }
378 0           return $self->{ttl};
379             }
380              
381             sub _service_base_uri {
382 0     0     return "ARecord";
383             }
384              
385             =item last_response
386              
387             This is the Net::Dynect::REST::Response object that was returned most recently
388             returned. Fromt his you can see stuff like when the request was submitted, and
389             how long it took to get a response.
390              
391             =cut
392              
393             sub last_response {
394 0     0 1   my $self = shift;
395 0 0         if (@_) {
396 0           my $new = shift;
397 0           $self->{last_response} = $new;
398             }
399 0           return $self->{last_response};
400             }
401              
402             sub _as_string {
403 0     0     my $self = shift;
404 0           my @texts;
405 0 0         push @texts, sprintf "FQDN '%s'", $self->fqdn if defined $self->fqdn;
406 0 0         push @texts, sprintf "Record Type '%s'", $self->record_type
407             if defined $self->record_type;
408 0 0         push @texts, sprintf "Record ID '%s'", $self->record_id
409             if defined $self->record_id;
410 0 0         push @texts, sprintf "TTL '%s'", $self->ttl if defined $self->ttl;
411 0 0         push @texts, sprintf "Zone '%s'", $self->zone if defined $self->zone;
412 0 0         push @texts, sprintf "RData '%s'", $self->rdata if defined $self->rdata;
413 0           return join( ', ', @texts );
414             }
415              
416             1;
417              
418             =back
419              
420             =head1 AUTHOR
421              
422             James Bromberger, james@rcpt.to
423              
424             =head1 SEE ALSO
425              
426             L, L, L, L.
427              
428             =head1 COPYRIGHT AND LICENSE
429              
430             Copyright (C) 2010 by James Bromberger
431              
432             This library is free software; you can redistribute it and/or modify
433             it under the same terms as Perl itself, either Perl version 5.10.1 or,
434             at your option, any later version of Perl 5 you may have available.
435              
436              
437             =cut