package WebService::Amazon::Route53::API::20110505;
use warnings;
use strict;
use Carp;
use URI::Escape;
use WebService::Amazon::Route53::API;
use parent 'WebService::Amazon::Route53::API';
sub new {
my ($class, %args) = @_;
my $self = $class->SUPER::new(%args);
$self->{api_version} = '2011-05-05';
$self->{api_url} = $self->{base_url} . $self->{api_version} . '/';
return $self;
}
sub list_hosted_zones {
my ($self, %args) = @_;
my $url = $self->{api_url} . 'hostedzone';
my $separator = '?';
if (defined $args{'marker'}) {
$url .= $separator . 'marker=' . uri_escape($args{'marker'});
$separator = '&';
}
if (defined $args{'max_items'}) {
$url .= $separator . 'maxitems=' . uri_escape($args{'max_items'});
}
my $response = $self->_request('GET', $url);
if (!$response->{success}) {
$self->_parse_error($response->{content});
return;
}
# Parse the returned XML data
my $data = $self->{'xs'}->XMLin($response->{content},
ForceArray => [ 'HostedZone' ]);
my $zones = [];
my $next_marker;
foreach my $zone_data (@{$data->{HostedZones}->{HostedZone}}) {
my $zone = {
'id' => $zone_data->{Id},
'name' => $zone_data->{Name},
'caller_reference' => $zone_data->{CallerReference},
};
if (exists $zone_data->{Config}) {
$zone->{config} = {};
if (exists $zone_data->{Config}->{Comment}) {
$zone->{config}->{comment} = $zone_data->{Config}->{Comment};
}
}
push(@$zones, $zone);
}
if (exists $data->{NextMarker}) {
$next_marker = $data->{NextMarker};
}
return wantarray ? ($zones, $next_marker) : $zones;
}
sub get_hosted_zone {
my ($self, %args) = @_;
if (!defined $args{'zone_id'}) {
carp "Required parameter 'zone_id' is not defined";
}
my $zone_id = $args{'zone_id'};
# Strip off the "/hostedzone/" part, if present
$zone_id =~ s!^/hostedzone/!!;
my $url = $self->{api_url} . 'hostedzone/' . $zone_id;
my $response = $self->_request('GET', $url);
if (!$response->{success}) {
$self->_parse_error($response->{content});
return;
}
my $data = $self->{'xs'}->XMLin($response->{content},
ForceArray => [ 'NameServer' ]);
my $zone = {
'id' => $data->{HostedZone}->{Id},
'name' => $data->{HostedZone}->{Name},
'caller_reference' => $data->{HostedZone}->{CallerReference}
};
if (exists $data->{HostedZone}->{Config}) {
$zone->{config} = {};
if (exists $data->{HostedZone}->{Config}->{Comment}) {
$zone->{config}->{comment} =
$data->{HostedZone}->{Config}->{Comment};
}
}
return $zone;
}
sub find_hosted_zone {
my ($self, %args) = @_;
if (!defined $args{'name'}) {
carp "Required parameter 'name' is not defined";
}
if ($args{'name'} !~ /\.$/) {
$args{'name'} .= '.';
}
my $found_zone = 0;
my $marker;
ZONES: while (1) {
my $zones = $self->list_hosted_zones(max_items => 100,
marker => $marker);
if (!defined $zones) {
# We can assume $self->{error} is already set
return;
}
my $zone;
foreach $zone (@$zones) {
if ($zone->{name} eq $args{'name'}) {
$found_zone = $zone;
last ZONES;
}
}
if (@$zones < 100) {
# Less than 100 zones have been returned -- no more zones to get
last ZONES;
}
else {
# Get the marker from the last returned zone
($marker = $zones->[@$zones-1]->{'id'}) =~ s!^/hostedzone/!!;
}
}
return $found_zone;
}
sub create_hosted_zone {
my ($self, %args) = @_;
if (!defined $args{'name'}) {
carp "Required parameter 'name' is not defined";
}
if (!defined $args{'caller_reference'}) {
carp "Required parameter 'caller_reference' is not defined";
}
# Make sure the domain name ends with a dot
if ($args{'name'} !~ /\.$/) {
$args{'name'} .= '.';
}
my $data = _ordered_hash(
'xmlns' => $self->{base_url} . 'doc/'. $self->{api_version} . '/',
'Name' => [ $args{'name'} ],
'CallerReference' => [ $args{'caller_reference'} ],
'HostedZoneConfig' => $args{'comment'} ? {
'Comment' => [ $args{'comment'} ]
} : undef,
);
my $xml = $self->{'xs'}->XMLout($data, SuppressEmpty => 1, NoSort => 1,
RootName => 'CreateHostedZoneRequest');
$xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $xml;
my $response = $self->_request('POST', $self->{api_url} . 'hostedzone',
{ content => $xml });
if (!$response->{success}) {
$self->_parse_error($response->{content});
return;
}
$data = $self->{xs}->XMLin($response->{content},
ForceArray => [ 'NameServer']);
my $ret = {
zone => {
id => $data->{HostedZone}->{Id},
name => $data->{HostedZone}->{Name},
caller_reference => $data->{HostedZone}->{CallerReference}
},
change_info => {
id => $data->{ChangeInfo}->{Id},
status => $data->{ChangeInfo}->{Status},
submitted_at => $data->{ChangeInfo}->{SubmittedAt}
},
delegation_set => {
name_servers => $data->{DelegationSet}->{NameServers}->{NameServer}
}
};
if (exists $data->{HostedZone}->{Config}) {
$ret->{zone}->{config} = {};
if (exists $data->{HostedZone}->{Config}->{Comment}) {
$ret->{zone}->{config}->{comment} =
$data->{HostedZone}->{Config}->{Comment};
}
}
return $ret;
}
sub delete_hosted_zone {
my ($self, %args) = @_;
if (!defined $args{'zone_id'}) {
carp "Required parameter 'zone_id' is not defined";
}
my $zone_id = $args{'zone_id'};
# Strip off the "/hostedzone/" part, if present
$zone_id =~ s!^/hostedzone/!!;
my $response = $self->_request('DELETE',
$self->{api_url} . 'hostedzone/' . $zone_id);
if (!$response->{success}) {
$self->_parse_error($response->{content});
return;
}
my $data = $self->{xs}->XMLin($response->{content});
my $change_info = {
id => $data->{ChangeInfo}->{Id},
status => $data->{ChangeInfo}->{Status},
submitted_at => $data->{ChangeInfo}->{SubmittedAt}
};
return $change_info;
}
sub list_resource_record_sets {
my ($self, %args) = @_;
if (!defined $args{'zone_id'}) {
carp "Required parameter 'zone_id' is not defined";
}
my $zone_id = $args{'zone_id'};
# Strip off the "/hostedzone/" part, if present
$zone_id =~ s!^/hostedzone/!!;
my $url = $self->{api_url} . 'hostedzone/' . $zone_id . '/rrset';
my $separator = '?';
if (defined $args{'name'}) {
$url .= $separator . 'name=' . uri_escape($args{'name'});
$separator = '&';
}
if (defined $args{'type'}) {
$url .= $separator . 'type=' . uri_escape($args{'type'});
$separator = '&';
}
if (defined $args{'identifier'}) {
$url .= $separator . 'identifier=' . uri_escape($args{'identifier'});
$separator = '&';
}
if (defined $args{'max_items'}) {
$url .= $separator . 'maxitems=' . uri_escape($args{'max_items'});
}
my $response = $self->_request('GET', $url);
if (!$response->{success}) {
$self->_parse_error($response->{content});
return;
}
my $data = $self->{'xs'}->XMLin($response->{content},
ForceArray => [ 'ResourceRecordSet', 'ResourceRecord' ]);
my $record_sets = [];
my $next_record;
foreach my $set_data (@{$data->{ResourceRecordSets}->{ResourceRecordSet}}) {
my $records = [];
foreach my $record (@{$set_data->{ResourceRecords}->{ResourceRecord}}) {
push(@$records, $record->{Value});
}
my $record_set = {
'name' => $set_data->{Name},
'type' => $set_data->{Type},
'ttl' => $set_data->{TTL},
'records' => $records
};
if (exists $set_data->{SetIdentifier}) {
$record_set->{set_identifier} = $set_data->{SetIdentifier}
}
if (exists $set_data->{Weight}) {
$record_set->{weight} = $set_data->{Weight}
}
# TODO: Add support for AliasTarget data
push(@$record_sets, $record_set);
}
if (exists $data->{NextRecordName}) {
$next_record = {
name => $data->{NextRecordName},
type => $data->{NextRecordType}
};
}
return wantarray ? ($record_sets, $next_record) : $record_sets;
}
sub change_resource_record_sets {
my ($self, %args) = @_;
if (!defined $args{'zone_id'}) {
carp "Required parameter 'zone_id' is not defined";
}
if (!defined($args{changes}) && !(defined($args{action}) &&
defined($args{name}) && defined($args{type}) &&
(defined($args{records}) || defined($args{value}))))
{
carp "Either the 'changes', or the 'action', 'name', 'type', " .
"and 'records'/'value' paremeters must be defined";
}
my $zone_id = $args{'zone_id'};
# Strip off the "/hostedzone/" part, if present
$zone_id =~ s!^/hostedzone/!!;
my $changes;
if (defined $args{'changes'}) {
$changes = $args{'changes'};
}
else {
# Simplified syntax for single changes
delete $args{'zone_id'};
$changes = [ \%args ];
}
my $data = _ordered_hash(
'xmlns' => $self->{base_url} . 'doc/' . $self->{api_version} . '/',
'ChangeBatch' => {
'Comment' => $args{'comment'} ? [
$args{'comment'}
] : undef,
'Changes' => [
{
'Change' => []
}
]
}
);
foreach my $change (@$changes) {
my $change_data = _ordered_hash(
'Action' => [ uc $change->{action} ],
'ResourceRecordSet' => _ordered_hash(
'Name' => [ $change->{name} ],
'Type' => [ $change->{type} ],
'TTL' => [ $change->{ttl} ],
'ResourceRecords' => [
{
'ResourceRecord' => []
}
]
)
);
if (exists $change->{records}) {
foreach my $value (@{$change->{records}}) {
push(@{$change_data->{ResourceRecordSet}->{ResourceRecords}[0]
->{ResourceRecord}}, { 'Value' => [ $value ] });
}
}
elsif (exists $change->{value}) {
push(@{$change_data->{ResourceRecordSet}->{ResourceRecords}[0]
->{ResourceRecord}}, { 'Value' => [ $change->{value} ] });
}
push(@{$data->{ChangeBatch}->{Changes}[0]->{Change}}, $change_data);
}
my $xml = $self->{'xs'}->XMLout($data, SuppressEmpty => 1, NoSort => 1,
RootName => 'ChangeResourceRecordSetsRequest');
$xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $xml;
my $response = $self->_request('POST',
$self->{api_url} . 'hostedzone/' . $zone_id . '/rrset',
{ content => $xml });
if (!$response->{success}) {
$self->_parse_error($response->{content});
return;
}
$data = $self->{xs}->XMLin($response->{content});
my $change_info = {
id => $data->{ChangeInfo}->{Id},
status => $data->{ChangeInfo}->{Status},
submitted_at => $data->{ChangeInfo}->{SubmittedAt}
};
return $change_info;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
WebService::Amazon::Route53::API::20110505
=head1 VERSION
version 0.101
=head1 METHODS
=head2 new
Creates a new instance of WebService::Amazon::Route53::API::20110505.
This method should not be used directly -- instead, call
L<WebService::Amazon::Route53>->new and pass the desired API version as the
C<version> argument.
=head2 list_hosted_zones
Gets a list of hosted zones.
Called in scalar context:
$zones = $r53->list_hosted_zones(max_items => 15);
Called in list context:
($zones, $next_marker) = $r53->list_hosted_zones(marker => '456ZONEID',
max_items => 15);
Parameters:
=over 4
=item * marker
Indicates where to begin the result set. This is the ID of the last hosted zone
which will not be included in the results.
=item * max_items
The maximum number of hosted zones to retrieve.
=back
Returns: A reference to an array of hash references, containing zone data.
Example:
$zones = [
{
'id' => '/hostedzone/123ZONEID',
'name' => 'example.com.',
'caller_reference' => 'ExampleZone',
'config' => {
'comment' => 'This is my first hosted zone'
}
},
{
'id' => '/hostedzone/456ZONEID',
'name' => 'example2.com.',
'caller_reference' => 'ExampleZone2',
'config' => {
'comment' => 'This is my second hosted zone'
}
}
];
When called in list context, it also returns the next marker to pass to a
subsequent call to C<list_hosted_zones> to get the next set of results. If this
is the last set of results, next marker will be C<undef>.
=head2 get_hosted_zone
Gets hosted zone data.
$zone = get_hosted_zone(zone_id => '123ZONEID');
Parameters:
=over 4
=item * zone_id
B<(Required)> Hosted zone ID.
=back
Returns: A reference to a hash containing zone data. Example:
$zone = {
'id' => '/hostedzone/123ZONEID'
'name' => 'example.com.',
'caller_reference' => 'ExampleZone',
'config' => {
'comment' => 'This is my first hosted zone'
}
};
=head2 find_hosted_zone
Finds the first hosted zone with the given name.
$zone = $r53->find_hosted_zone(name => 'example.com.');
Parameters:
=over 4
=item * name
B<(Required)> Hosted zone name.
=back
Returns: A reference to a hash containing zone data (see L<"get_hosted_zone">),
or C<0> if there is no hosted zone with the given name.
=head2 create_hosted_zone
Creates a new hosted zone.
$response = $r53->create_hosted_zone(name => 'example.com.',
caller_reference => 'example.com_01');
Parameters:
=over 4
=item * name
B<(Required)> New hosted zone name.
=item * caller_reference
B<(Required)> A unique string that identifies the request.
=back
Returns: A reference to a hash containing new zone data, change description,
and name servers information. Example:
$response = {
'zone' => {
'id' => '/hostedzone/123ZONEID'
'name' => 'example.com.',
'caller_reference' => 'example.com_01',
'config' => {}
},
'change_info' => {
'id' => '/change/123CHANGEID'
'submitted_at' => '2011-08-30T23:54:53.221Z',
'status' => 'PENDING'
},
'delegation_set' => {
'name_servers' => [
'ns-001.awsdns-01.net',
'ns-002.awsdns-02.net',
'ns-003.awsdns-03.net',
'ns-004.awsdns-04.net'
]
},
};
=head2 delete_hosted_zone
Deletes a hosted zone.
$change_info = $r53->delete_hosted_zone(zone_id => '123ZONEID');
Parameters:
=over 4
=item * zone_id
B<(Required)> Hosted zone ID.
=back
Returns: A reference to a hash containing change information. Example:
$change_info = {
'id' => '/change/123CHANGEID'
'submitted_at' => '2011-08-31T00:04:37.456Z',
'status' => 'PENDING'
};
=head2 list_resource_record_sets
Lists resource record sets for a hosted zone.
Called in scalar context:
$record_sets = $r53->list_resource_record_sets(zone_id => '123ZONEID');
Called in list context:
($record_sets, $next_record) =
$r53->list_resource_record_sets(zone_id => '123ZONEID');
Parameters:
=over 4
=item * zone_id
B<(Required)> Hosted zone ID.
=item * name
The first domain name (in lexicographic order) to retrieve.
=item * type
DNS record type of the next resource record set to retrieve.
=item * identifier
Set identifier for the next source record set to retrieve. This is needed when
the previous set of results has been truncated for a given DNS name and type.
=item * max_items
The maximum number of records to be retrieved. The default is 100, and it's the
maximum allowed value.
=back
Returns: A reference to an array of hash references, containing record set data.
Example:
$record_sets = [
{
name => 'example.com.',
type => 'MX'
ttl => 86400,
records => [
'10 mail.example.com'
]
},
{
name => 'example.com.',
type => 'NS',
ttl => 172800,
records => [
'ns-001.awsdns-01.net.',
'ns-002.awsdns-02.net.',
'ns-003.awsdns-03.net.',
'ns-004.awsdns-04.net.'
]
}
];
When called in list context, it also returns a reference to a hash, containing
information on the next record which can be passed to a subsequent call to
C<list_resource_record_sets> to get the next set of records (using the C<name>
and C<type> parameters). Example:
$next_record = {
name => 'www.example.com.',
type => 'A'
};
If this is the last set of records, next record will be C<undef>.
=head2 change_resource_record_sets
Makes changes to DNS record sets.
$change_info = $r53->change_resource_record_sets(zone_id => '123ZONEID',
changes => [
# Delete the current A record
{
action => 'delete',
name => 'www.example.com.',
type => 'A',
ttl => 86400,
value => '12.34.56.78'
},
# Create a new A record with a different value
{
action => 'create',
name => 'www.example.com.',
type => 'A',
ttl => 86400,
value => '34.56.78.90'
},
# Create two new MX records
{
action => 'create',
name => 'example.com.',
type => 'MX',
ttl => 86400,
records => [
'10 mail.example.com',
'20 mail2.example.com'
]
}
]);
If there is just one change to be made, you can use the simplified call syntax,
and pass the change parameters directly, instead of using the C<changes>
parameter:
$change_info = $r53->change_resource_record_sets(zone_id => '123ZONEID',
action => 'delete',
name => 'www.example.com.',
type => 'A',
ttl => 86400,
value => '12.34.56.78');
Parameters:
=over 4
=item * zone_id
B<(Required)> Hosted zone ID.
=item * changes
B<(Required)> A reference to an array of hashes, describing the changes to be
made. If there is just one change, the array may be omitted and change
parameters may be passed directly.
=back
Change parameters:
=over 4
=item * action
B<(Required)> The action to perform (C<"create"> or C<"delete">).
=item * name
B<(Required)> The name of the domain to perform the action on.
=item * type
B<(Required)> The DNS record type.
=item * ttl
The DNS record time to live (TTL), in seconds.
=item * records
A reference to an array of strings that represent the current or new record
values. If there is just one value, you can use the C<value> parameter instead.
=item * value
Current or new DNS record value. For multiple record values, use the C<records>
parameter.
=back
Returns: A reference to a hash containing change information. Example:
$change_info = {
'id' => '/change/123CHANGEID'
'submitted_at' => '2011-08-31T00:04:37.456Z',
'status' => 'PENDING'
};
=head2 error
Returns the last error.
$error = $r53->error;
Returns: A reference to a hash containing the type, code, and message of the
last error. Example:
$error = {
'type' => 'Sender',
'message' => 'FATAL problem: UnsupportedCharacter encountered at ',
'code' => 'InvalidDomainName'
};
=head1 AUTHOR
Michal Wojciechowski <odyniec@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2011 by Michal Wojciechowski.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut