package Net::Fastly;
use strict;
use warnings;
use Net::Fastly::Client;
use Net::Fastly::Invoice;
use Net::Fastly::Settings;
our $VERSION = "1.05";
BEGIN {
no strict 'refs';
our @CLASSES = qw(Net::Fastly::User Net::Fastly::Customer
Net::Fastly::Backend Net::Fastly::Director
Net::Fastly::Domain Net::Fastly::Healthcheck
Net::Fastly::Match Net::Fastly::Origin
Net::Fastly::Service Net::Fastly::Syslog
Net::Fastly::VCL Net::Fastly::Version
Net::Fastly::Condition);
foreach my $class (@CLASSES) {
my $file = $class . '.pm';
$file =~ s{::}{/}g;
CORE::require($file);
$class->import;
my $name = $class->_path;
foreach my $method (qw(get create update delete list)) {
my $code = "sub { shift->_$method('$class', \@_) }";
my $glob = "${method}_${name}";
$glob .= "s" if $method eq 'list';
# don't create this if it's a list and something isn't listable ...
next if $method eq 'list' && $class->_skip_list;
# or if it already exists (i.e it's been overidden)
next if defined *$glob;
*$glob = eval "$code";
}
}
};
=head1 NAME
Net::Fastly - client library for interacting with the Fastly web acceleration service
=head1 SYNOPSIS
use Net::Fastly;
my $fastly = Net::Fastly->new(%login_opts);
my $current_user = $fastly->current_user;
my $current_customer = $fastly->current_customer;
my $user = $fastly->get_user($current_user->id);
my $customer = $fastly->get_customer($current_customer->id);
print "Name: ".$user->name."\n";
print "Works for ".$user->customer->name."\n";
print "Which is the same as ".$customer->name."\n";
print "Which has the owner ".$customer->owner->name."\n";
# Let's see which services we have defined
foreach my $service ($fastly->list_services) {
print $service->name." (".$service->id.")\n";
foreach my $version ($service->versions) {
print "\t".$version->number."\n";
}
}
my $service = $fastly->create_service(name => "MyFirstService");
my $latest_version = $service->version;
# Create a domain and a backend for the service ...
my $domain = $fastly->create_domain(service_id => $service->id, version => $latest_version->number, name => "www.example.com");
my $backend = $fastly->create_backend(service_id => $service->id, version => $latest_version->number, ipv4 => "127.0.0.1", port => 80);
# ... and activate it. You're now hosted on Fastly.
$latest_version->activate;
# Let's take a peek at the VCL that Fastly generated for us
my $vcl = $latest_version->generated_vcl;
print "Generated VCL file is:\n".$vcl->content."\n";
# Now let's create a new version ...
my $new_version = $latest_version->clone;
# ... add a new backend ...
my $new_backend = $fastly->create_backend(service_id => $service->id, version => $new_version->number, ipv4 => "192.0.0.1", port => 8080);
# ... and upload some custome vcl (presuming we have permissions)
$new_version->upload_vcl($vcl_name, slurp($vcl_file));
$new_version->activate;
# Purging
$fastly->purge('http://www.example.com'); # regular purge
$fastly->purge('http://www.example.com', 1); # 'soft' purge (see note below)
$service->purge_by_key('article-1'); # purge by surrogate key, note this works on $service
$service->purge_by_key('article-1', 1); # 'soft' purge by surrogate key
$service->purge_all; # use with caution!
=head1 DESCRIPTION
=head1 METHODS
=cut
=head2 new <opt[s]>
Create a new Fastly client. Options are
=over 4
=item user - your Fastly login
=item password - your Fastly password
=item api_key - your Fastly api key
=back
You only need to pass in C<api_key> OR C<user> and C<password>.
Some methods require full username and password rather than just auth token.
=cut
sub new {
my $class = shift;
my %opts = @_;
my ($client, $user, $customer) = Net::Fastly::Client->new(%opts);
my $self = bless { _client => $client, _current_customer => undef, _current_user => undef}, $class;
if ($user && $customer) {
$self->{_current_user} = Net::Fastly::User->new($self, %$user);
$self->{_current_customer} = Net::Fastly::Customer->new($self, %$customer);
}
return $self;
}
=head2 client
Get the current Net::Fastly::Client
=cut
sub client { shift->{_client} }
=head2 set_customer <customer id>
Set the current customer to act as.
B<NOTE>: this will only work if you're an admin
=cut
sub set_customer {
my $self = shift;
my $id = shift;
die "You must be fully authed to set the customer" unless $self->fully_authed;
die "You must be an admin to set the customer" unless $self->current_user->can_do('admin');
delete $self->{_current_customer};
$self->client->set_customer($id);
}
=head2 authed
Whether or not we're authed at all by either username & password or API key
=cut
sub authed { shift->client->authed }
=head2 fully_authed
Whether or not we're fully (username and password) authed
=cut
sub fully_authed { shift->client->fully_authed }
=head2 current_user
Return a User object representing the current logged in user.
This will not work if you're logged in with an API key.
=cut
sub current_user {
my $self = shift;
die "You must be fully authed to get the current user" unless $self->fully_authed;
$self->{_current_user} ||= $self->_get("Net::Fastly::User");
}
=head2 current_customer
Return a Customer object representing the customer of the current logged in user.
=cut
sub current_customer {
my $self = shift;
die "You must be authed to get the current customer" unless $self->authed;
$self->{_current_customer} ||= $self->_get("Net::Fastly::Customer");
}
=head2 commands
Return a hash representing all commands available.
Useful for information.
=cut
sub commands {
my $self = shift;
return $self->{__cache_commands} if ($self->{__cache_commands});
return eval { $self->{__cache_commands} = $self->client->_get('/commands') };
}
=head2 purge <path> [soft]
Purge the specified path from your cache.
You can optionally pass in a true value to enable "soft" purging e.g
$fastly->purge($url, 1);
See L<https://docs.fastly.com/guides/purging/soft-purges>
Previously purging made an API call to the C</purge> endpoint of the Fastly API.
The new method of purging is done by making an HTTP request against the URL using the C<PURGE> HTTP method.
This module now uses the new method. The old method can be used by passing the C<use_old_purge_method> into the constructor.
my $fastly = Net::Fastly->new(%login_opts, use_old_purge_method => 1);
=cut
sub purge {
my $self = shift;
my $url = shift;
my $soft = shift;
$self->client->_purge($url, headers => { 'Fastly-Soft-Purge' => $soft });
}
=head2 stats [opt[s]]
Fetches historical stats for each of your fastly services and groups the results by service id.
If you pass in a C<field> opt then fetches only the specified field.
If you pass in a C<service> opt then fetches only the specified service.
The C<field> and C<service> opts can be combined.
If you pass in an C<aggregate> flag then fetches historical stats information aggregated across all of your Fastly services. This cannot be combined with C<field> and C<service>.
Other options available are:
=over 4
=item from & to
=item by
=item region
=back
See http://docs.fastly.com/docs/stats for details.
=cut
sub stats {
my $self = shift;
my %opts = @_;
die "You can't specify a field or a service for an aggregate request" if $opts{aggregate} && ($opts{field} || $opts{service});
my $url = "/stats";
if (delete $opts{aggregate}) {
$url .= "/aggregate";
}
if (my $service = delete $opts{service}) {
$url .= "/service/$service";
}
if (my $field = delete $opts{field}) {
$url .= "/field/$field";
}
$self->client->_get_stats($url, %opts);
}
=head2 usage [opt[s]]
Returns usage information aggregated across all Fastly services and grouped by region.
If the C<by_service> flag is passed then teturns usage information aggregated by service and grouped by service & region.
Other options available are:
=over 4
=item from & to
=item by
=item region
=back
See http://docs.fastly.com/docs/stats for details.
=cut
sub usage {
my $self = shift;
my %opts = @_;
my $url = "/stats/usage";
$url .= "_by_service" if delete $opts{by_service};
$self->client->_get_stats($url, %opts);
}
=head2 regions
Fetches the list of codes for regions that are covered by the Fastly CDN service.
=cut
sub regions {
my $self = shift;
$self->client->_get_stats("/stats/regions");
}
=head2 create_user <opts>
=head2 create_customer <opts>
=head2 create_service <opts>
=head2 create_version service_id => <service id>, [opts]
=head2 create_backend service_id => <service id>, version => <version number>, name => <name> <opts>
=head2 create_director service_id => <service id>, version => <version number>, name => <name> <opts>
=head2 create_domain service_id => <service id>, version => <version number>, name => <name> <opts>
=head2 create_healthcheck service_id => <service id>, version => <version number>, name => <name> <opts>
=head2 create_match service_id => <service id>, version => <version number>, name => <name> <opts>
=head2 create_origin service_id => <service id>, version => <version number>, name => <name> <opts>
=head2 create_syslog service_id => <service id>, version => <version number>, name => <name> <opts>
=head2 create_vcl service_id => <service id>, version => <version number>, name => <name> <opts>
=head2 create_condition service_id => <service id>, version => <version number>, name => <name> <opts>
Create new objects.
=cut
=head2 get_user <id>
=head2 get_customer <id>
=head2 get_service <id>
=head2 get_version <service id> <number>
=head2 get_backend <service id> <version number> <name>
=head2 get_director <service id> <version number> <name>
=head2 get_domain <service id> <version number> <name>
=head2 get_healthcheck <service id> <version number> <name>
=head2 get_invoice [<year> <month>]
Return a Net::Fastly::Invoice objects representing an invoice for all services.
If a year and month are passed in returns the invoice for that whole month.
Otherwise it returns the invoices for the current month to date.
=head2 get_match <service id> <version number> <name>
=head2 get_origin <service id> <version number> <name>
=head2 get_syslog <service id> <version number> <name>
=head2 get_vcl <service id> <version number> <name>
=head2 get_version <service id> <version number> <name>
=head2 get_settings <service id> <version number>
=head2 get_condition <service id> <version number> <name>
Get existing objects.
=cut
=head2 update_user <obj>
=head2 update_customer <obj>
=head2 update_service <obj>
=head2 update_version <obj>
=head2 update_backend <obj>
=head2 update_director <obj>
=head2 update_domain <obj>
=head2 update_healthcheck <obj>
=head2 update_match <obj>
=head2 update_origin <obj>
=head2 update_syslog <obj>
=head2 update_vcl <obj>
=head2 update_version <obj>
=head2 update_settings <obj>
=head2 update_condition <obj>
Update existing objects.
Note - you can also do
$obj->save;
=cut
=head2 delete_user <obj>
=head2 delete_customer <obj>
=head2 delete_service <obj>
=head2 delete_version <obj>
=head2 delete_backend <obj>
=head2 delete_director <obj>
=head2 delete_domain <obj>
=head2 delete_healthcheck <obj>
=head2 delete_match <obj>
=head2 delete_origin <obj>
=head2 delete_syslog <obj>
=head2 delete_vcl <obj>
=head2 delete_version <obj>
=head2 delete_condition <obj>
Delete existing objects.
Note - you can also do
$obj->delete
=cut
=head2 list_users
=head2 list_customers
=head2 list_versions
=head2 list_services
=head2 list_backends
=head2 list_directors
=head2 list_domains
=head2 list_healthchecks
=head2 list_matchs
=head2 list_origins
=head2 list_syslogs
=head2 list_vcls
=head2 list_versions
=head2 list_conditions
Get a list of all objects
=head2 search_services <param[s]>
Search all the services that the current customer has.
In general you'll want to do
my @services = $fastly->search_services(name => $name);
or
my ($service) = $fastly->search_services(name => $name, version => $number);
=cut
use Carp;
sub _list {
my $self = shift;
my $class = shift;
my %opts = @_;
my $list = $self->client->_get($class->_list_path(%opts), %opts);
return () unless $list;
return map { $class->new($self, %$_) } @$list;
}
sub _get {
my $self = shift;
my $class = shift;
my $hash;
if (@_) {
$hash = $self->client->_get($class->_get_path(@_));
} else {
$hash = $self->client->_get("/current_".$class->_path);
}
return undef unless $hash;
return $class->new($self, %$hash);
}
sub _create {
my $self = shift;
my $class = shift;
my %args = @_;
my $hash = $self->client->_post($class->_post_path(%args), %args);
return $class->new($self, %$hash);
}
sub _update {
my $self = shift;
my $class = shift;
my $obj = shift;
my %fds = $obj->_as_hash;
my $hash = $self->client->_put($class->_put_path($obj), map { $_ => $fds{$_} } grep { $_ !~ m/^(service_id|version)$/ } keys %fds);
return $class->new($self, %$hash);
}
sub _delete {
my $self = shift;
my $class = shift;
my $obj = shift;
$obj = bless $obj, $class if 'HASH' eq ref($obj);
return defined $self->client->_delete($class->_delete_path($obj));
}
=head1 CLASS METHODS
=head2 load_options <file>
Attempts to load various config options in the form
<key> = <value>
From a file.
Skips whitespace and lines starting with C<#>.
=cut
sub load_options {
my $file = shift;
my %options = ();
return %options unless -f $file;
open(my $fh, $file) || die "Couldn't open $file: $!\n";
while (<$fh>) {
chomp;
next if /^#/;
next if /^\s*$/;
next unless /=/;
s/(^\s*|\s*$)//g;
my ($key, $val) = split /\s*=\s*/, $_, 2;
$options{$key} = $val;
}
close($fh);
return %options;
}
=head2 get_options <file[s]>
Tries to load options from the file[s] passed in using,
C<load_options>, stopping when it finds the first one.
Then it overrides those options with command line options
of the form
--<key>=<value>
=cut
sub get_options {
my @configs = @_;
my %options;
foreach my $config (@configs) {
next unless -f $config;
%options = load_options($config);
last;
}
while (@ARGV && $ARGV[0] =~ m!^-+(\w+)\=(.+)$!) {
if ($1 eq "config") {
die "No such file '$2'" unless -f $2;
my %tmp = load_options($2);
$options{$_} = $tmp{$_} for keys %tmp;
} else {
$options{$1} = $2;
}
shift @ARGV;
}
die "Couldn't find options from command line arguments or ".join(", ", @configs)."\n" unless keys %options;
return %options;
}
=head1 COPYING
Copyright 2011 - Fastly Inc
Distributed under the same terms as Perl itself.
=head1 SUPPORT
Mail support at fastly dot com if you have problems.
=head1 DEVELOPERS
http://github.com/fastly/fastly-perl
http://www.fastly.com/documentation
=cut
1;