#!perl -w
use strict;
use warnings;
use Net::Fastly;
use File::Basename;
use Data::Dumper;
use POSIX qw(strftime);
use YAML;
BEGIN {
eval {
require Term::ShellUI;
Term::ShellUI->import();
require Term::ANSIColor;
Term::ANSIColor->import(qw(:constants));
};
die "You must install Term::ShellUI and Term::ANSIColorin order to run the Fastly shell\n" if $@;
}
=head1 NAME
fastly - a command line shell for interacting with the Fastly infrastructure
=head1 USAGE
fastly [option[s]]
=head1 CONFIGURATION
You can either have a config file in either ~/.fastly or /etc/fastly with
user = <login>
password = <password>
B<NOTE:> For the time being you must use username and password, not API key. This may change in the future.
Alternatively you can pass in any of those options on the command line
fastly --user=<login> --password=<password>
=head1 PROXYING
There are three ways to proxy:
The first method is to put a proxy option in your .fastly file (or pass it in on)
proxy = http://localhost:8080
The second is to pass it in on the command line
fastly --user <login> --password <password> --proxy http://localhost:8080
Lastly, the third method is to set your C<https_proxy> environment variable. So, in Bash
% export https_proxy=http://localhost:8080
or in CSH or TCSH
% setenv https_proxy=http://localhost:8080
=head1 DESCRIPTION
=cut
my %opts = Net::Fastly::get_options($ENV{HOME}."/.fastly", "/etc/fastly");
my $fastly = Net::Fastly->new(%opts);
my $customer = $fastly->current_customer;
my $term = Term::ShellUI->new(app => "fastly", keep_quotes => 0, history_file => $ENV{HOME}."/.fastly_history", prompt => "fastly> ");
#$term->{debug_complete}=5;
print 'Using '.$term->{term}->ReadLine."\n";
# precache the list of services
my $basecommands = {
"help" => {
desc => "Print helpful information",
args => sub { shift->help_args(undef, @_); },
method => sub { shift->help_call(undef, @_); }
},
"quit" => {
desc => "Quit",
maxargs => 0,
method => sub { shift->exit_requested(1) },
exclude_from_history => 1,
},
"who" => {
desc => "Print who is logged in",
proc => \&who,
},
"show" => {
desc => "Display objects",
cmds => {
service => {
desc => "Show a service",
doc => "Lists services",
minargs => 1,
maxargs => 3,
args => [\&list_service_complete, sub { ['version'] }, \&list_version],
proc => \&show_service,
},
services => {
desc => "List services",
proc => \&show_services,
},
versions => {
desc => "Show all the versions for a service",
cmds => {
service => {
desc => "The service name",
minargs => 1,
maxargs => 1,
args => [\&list_service_complete ],
proc => \&show_versions,
}
}
},
stats => {
desc => "Show stats for a service",
cmds => {
service => {
desc => "The service name",
minargs => 1,
args => [\&list_service_complete, sub { ['aggregate'] }, sub { ['by']}, sub { [qw(minute hour day)]}, sub { ['split'] }, sub { ['by']} , sub { ['datacenter'] }, undef],
proc => \&show_stats_service,
},
# all => {
# args => [sub { ['aggregate'] }, sub { ['by']}, sub { [qw(minute hour day)]}, sub { ['split'] }, sub { ['by']} , sub { ['datacenter'] }, undef],
# proc => \&show_stats_all,
# }
}
},
diff => {
desc => "Difference between two versions",
minargs => 5,
maxargs => 5,
proc => \&diff,
args => [\&list_service_complete, sub { ['version'] }, \&list_version, sub { ['to'] }, \&list_version, undef],
},
}
},
"create" => {
desc => "Create a new object",
cmds => {
service => {
desc => "The service name",
minargs => 1,
args => [\&list_service_complete, sub { ['version'] }, \&list_version, \&service_models, \&name_map_complete, \&model_args],
proc => \&create,
},
},
},
"add" => {
desc => "Add a backend to a director or a director to an origin",
cmds => {
service => {
desc => "The service name",
minargs => 1,
args => [\&list_service_complete, sub { ['version'] }, \&list_version, sub { ['backend', 'director'] }, \&list_target, sub { ['to'] }, \&list_target_type, \&list_target],
proc => \&add,
},
}
},
"delete" => {
desc => "Delete an object",
cmds => {
service => {
desc => "The service name",
minargs => 1,
args => [\&list_service_complete, sub { ['version'] }, \&list_version, \&service_models, \&list_objects, undef],
proc => \&delete,
}
}
},
"set" => {
desc => "Update an object",
cmds => {
service => {
desc => "The service name",
minargs => 5,
args => [\&list_service_complete, sub { ['version'] }, \&list_version, \&service_models, \&list_objects, \&model_args],
proc => \&set,
},
customer => {
desc => "Assume a customer",
args => [\&list_customers_complete],
proc => \&set_customer,
}
}
},
"upload" => {
desc => "Upload a custom VCL file",
cmds => {
service => {
desc => "The service name",
minargs => 5,
args => [\&list_service_complete, sub { ['version'] }, \&list_version, sub { ['vcl']}, \&list_objects, sub { ['from'] }, sub { ['file'] },
sub { shift->complete_files(@_) }, undef],
proc => \&upload,
}
}
},
"download" => {
desc => "Download a vcl file",
cmds => {
service => {
desc => "The service name",
minargs => 5,
args => [\&list_service_complete, sub { ['version'] }, \&list_version, sub { ['vcl'] }, \&list_objects, sub { ['to'] }, sub { ['file'] }, undef],
proc => \&download,
}
}
},
"validate" => {
desc => "Check to see that the currently uploaded VCL is valid",
cmds => {
service => {
desc => "The service name",
minargs => 3,
maxargs => 3,
args => [\&list_service_complete, sub { ['version'] }, \&list_version],
proc => \&validate,
}
}
},
"dump" => {
desc => "Show the generated VCL for a given service",
cmds => {
service => {
desc => "The service name",
minargs => 3,
maxargs => 3,
args => [\&list_service_complete, sub { ['version'] }, \&list_version],
proc => \&dump,
}
}
},
"activate" => {
desc => "Activate a version for use",
cmds => {
service => {
desc => "The service name",
minargs => 3,
maxargs => 3,
args => [\&list_service_complete, sub { ['version'] }, \&list_version],
proc => \&activate,
}
}
},
"deactivate" => {
desc => "Deactive a service (note, this makes it not serve)",
cmds => {
service => {
desc => "The service name",
minargs => 1,
maxargs => 3,
args => [\&list_service_complete, sub { ['version'] }, \&list_version],
proc => \&deactivate,
}
}
},
"purge" => {
desc => "Remove objects from the cache",
cmds => {
service => {
desc => "The service name",
minargs => 1,
maxargs => 1,
args => [\&list_service_complete],
proc => \&purge_service,
},
url => {
desc => "A url",
minargs => 1,
maxargs => 1,
proc => \&purge_url,
},
key => {
desc => "The service name",
minargs => 3,
maxargs => 3,
args => [undef, sub { ['service' ] }, \&list_service_complete],
proc => \&purge_key,
}
}
},
"clone" => {
desc => "Clone a configuration so that the new version can be modified",
cmds => {
service => {
desc => "The service name",
minargs => 3,
maxargs => 3,
args => [\&list_service_complete, sub { ['version'] }, \&list_version],
proc => \&clone,
}
}
}
};
sub who {
if ($fastly->client->fully_authed) {
my $user = $fastly->current_user;
print "Logged in as ".$user->name." <".$user->login."> of ".$user->customer->name." [". $user->customer->id ."]\n";
print "Currently acting as customer ".$customer->name." [".$customer->id."]\n" unless $customer->id eq $user->customer->id;
} else {
print "Logged with api key as ".$customer->name."\n";
}
}
sub set_customer {
my $name = shift;
return (BOLD, RED, "You are not an admin", RESET, "\n") unless $fastly->fully_authed && $fastly->current_user->can_do('admin');
my %customers = map { $_->name => $_ } $fastly->list_customers;
my $tmp = $customers{$name};
unless ($tmp) {
print_error("Can't find the customer '$name'", RESET, "\n");
return;
}
$fastly->set_customer($tmp->id);
$customer = $tmp;
print "Set assumed customer to ".$customer->name." [".$customer->id."]\n";
}
sub name_map_complete {
my $t = shift;
my $c = shift;
my $commands = $fastly->commands;
my $command = $commands->{"$c->{args}->[3].create"};
if ($command->{name}) {
return (RED . " \nNAME" . RESET);
} else {
return ["MAP"];
}
}
sub show_stats_service {
my $service_name = shift;
my ($service) = $fastly->search_services(name => $service_name);
show_stats($service, @_);
}
sub show_stats {
my $service = shift;
my $type = "minutely";
if ($_[2]) {
$type = "hourly" if ($_[2] eq 'hour');
$type = "daily" if ($_[2] eq 'day');
}
my $stats = eval { $service->stats($type) };
unless ($stats) {
my $str = "$@";
print BOLD, RED, "Error: ", RESET, $str,"\n";
return;
}
my $split = 1;
printf(BOLD."%-21s%-5s%-10s%-10s%-6s%-10s%-6s%5s %-7s %-7s %-7s".RESET."\n", "Time", "DC", "Bytes", "Requests", "Hits", "Misses", "Pass", "%", "200", "304", "5xx");
unless (keys %$stats) {
print BOLD, RED, "No stats available", RESET, "\n";
return;
}
my @stats;
my $last;
my $total = {start_time => time, datacenter => ''} ;
foreach my $datacenter (keys %$stats) {
my $stat = $stats->{$datacenter};
next unless ref($stat);
$stat->{datacenter} = $datacenter;
$stat->{start_time} = $stats->{recorded};
foreach (qw(hits requests pass uncacheable body_size header_size miss status_5xx status_200 status_304)) {
$total->{$_} ||= 0;
$total->{$_} += $stat->{$_} || 0;
}
if ($split) {
push @stats, $stat;
} else {
unless ($last) {
$last = $stat;
next;
}
if ($last->{start_time} != $stat->{start_time}) {
push @stats, $last;
$last = $stat;
} else {
foreach (qw(hits requests pass uncacheable body_size header_size miss status_5xx status_200 status_304)) {
$last->{$_} += $stat->{$_};
}
}
}
}
push @stats, $last unless $split;
push @stats, undef, $total;
foreach my $stats (@stats) {
unless ($stats && keys %$stats>2) {
print "----\n";
next;
}
my $ymd = strftime "%Y:%m:%d", gmtime($stats->{start_time});
my $hms = strftime "%H:%M:%S", gmtime($stats->{start_time});
my $size = $stats->{body_size} + $stats->{header_size};
if ($size > 1024*1024*1024) {
$size = sprintf("%.2f GB", $size / 1024 / 1024 / 1024);
} elsif ($size > 1024*1024) {
$size = sprintf("%.2f MB", $size / 1024 / 1024);
} elsif ($size > 1024) {
$size = sprintf("%.2f KB", $size / 1024);
} else {
$size = sprintf("%d B", $size);
}
my $hitp = "00.0%";
if ($stats->{hits}) {
$hitp = sprintf("%.1f%%", (($stats->{hits}/($stats->{requests}-$stats->{pass}-$stats->{uncacheable}))*100));
}
my $dc = "ALL";
$dc = $stats->{datacenter} if ($split);
printf("%s%-11s%-10s%s%s%-5s%s%-10s%-10s%-6s%-10s%-6s%-6s %-7s %-7s %s%-7s%s\n", RED, $ymd, $hms, RESET, BLUE, $dc, RESET, $size, $stats->{requests}, $stats->{hits}, $stats->{miss}, $stats->{pass}, $hitp, $stats->{status_200}, $stats->{status_304}, RED, $stats->{status_5xx}, RESET);
}
}
sub clone {
my $service_name = shift;
shift;
my $version_number = shift;
my ($service) = $fastly->search_services(name => $service_name);
my $version = $service->version($version_number);
my $new = eval { $version->clone };
if ($@) {
print_error($@);
} else {
print "Version $version_number cloned to ".$new->number."\n";
}
}
sub dump {
my $service_name = shift;
shift;
my $version_number = shift;
my ($service) = $fastly->search_services(name => $service_name);
my $version = $service->version($version_number);
my $vcl = eval { $version->generated_vcl };
if ($@) {
print_error($@);
} else {
print present({$vcl->_as_hash});
}
}
sub validate {
my $service_name = shift;
shift;
my $version_number = shift;
my ($service) = $fastly->search_services(name => $service_name);
my $version = $service->version($version_number);
my $valid = eval { $version->validate };
if ($@) {
print_error($@);
} else {
print "Config validated\n";
}
}
sub activate {
my $service_name = shift;
shift;
my $version_number = shift;
my ($service) = $fastly->search_services(name => $service_name);
my $version = $service->version($version_number);
my $response = eval { $version->activate};
if ($@) {
print_error($@);
} else {
print "Set $service_name version ".$version->number." to active\n";
}
}
sub deactivate {
my $service_name = shift;
shift;
my $version_number = shift;
my ($service) = $fastly->search_services(name => $service_name);
my $version = $service->version($version_number);
my $response = eval { $version->deactivate};
if ($@) {
print_error($@);
} else {
print "Service $service_name version ".$version->number." has been deactivated.\n";
}
}
sub purge_service {
my $service_name = shift;
my $version = shift;
my ($service) = $fastly->search_services(name => $service_name);
my $ok = eval { $service->purge_all };
if ($@) {
print_error($@);
} else {
print "Purged\n";
}
}
sub purge_url {
my $url = shift;
my $ok = eval { $fastly->purge($url) };
if ($@) {
print_error($@);
} else {
print "Purged\n";
}
}
sub purge_key {
my $key = shift;
shift;
my $service_name = shift;
my ($service) = $fastly->search_services(name => $service_name);
my $ok = eval { $service->purge_by_key($key) };
if ($@) {
print_error($@);
} else {
print "Purged\n";
}
}
use File::Temp qw(tempfile);
sub diff {
my $service_name = shift;
shift;
my $from_version = shift;
shift;
my $to_version = shift;
my $from = eval { $fastly->search_services(name => $service_name, version => $from_version) };
if ($@) { print_error($@); }
my $to = eval { $fastly->search_services(name => $service_name, version => $to_version) };
if ($@) { print_error($@); }
if ($to->{type} eq 'error') { print BOLD, RED, "Error: ", RESET, $to->{data}->{error},"\n"; return };
my ($to_fh, $to_fn) = tempfile();
my ($from_fh, $from_fn) = tempfile();
print $to_fh present({$to->_as_hash});
print $from_fh present({$from->_as_hash});
my $diff = `diff -u $from_fn $to_fn`;
print "$diff";
print "\n\n";
}
sub set {
my $service_name = shift;
shift;
my $version = shift;
my $type = shift;
my $service = eval { $fastly->search_services(name => $service_name ) };
if ($@ || !$service) {
my $str = "$@";
print BOLD, RED, "Error finding service $service_name: ", RESET, $str,"\n";
return;
}
my $name = shift;
my $method = "get_$type";;
my $obj = eval { $fastly->$method($service->id, $version, $name) };
if ($@ || !$obj) {
my $str = "$@";
print BOLD, RED, "Error finding $type $name: ", RESET, $str,"\n";
return;
}
while (my $prop = shift @_) {
my $val = shift @_ || last;
if ($prop eq 'name') {
print BOLD, RED, "You can't update the name on a $type", RESET,"\n";
return
}
unless ($obj->can($prop)) {
print BOLD, RED, "$type doesn't have the property '$prop' ", RESET,"\n";
return
}
$obj->$prop($val);
}
$method = "update_$type";
my $o = eval { $fastly->$method($obj) };
if ($@) {
print_error($@);
} else {
print "Update successful\n";
}
return;
}
#
# # sub error_completion {
# # my $t = shift;
# # my $r = shift;
# # if ($r->{type} eq 'error') {
# # $t->completemsg(sprintf("%s%sError: %s %s\n", BOLD, RED, RESET, $r->{data}->{error}));
# # return 1;
# # }
# # return 0;
# # }
#
# # sub error {
# # my $r = shift;
# # if ($r->{type} eq 'error') {
# # print BOLD, RED, "Error: ", RESET, $r->{data}->{error},"\n";
# # return 1;
# # }
# # return 0;
# # }
#
sub print_error {
my $error = shift;
chomp($error);
print BOLD, RED, "Error: ", RESET, RED, $error . RESET . "\n";
}
sub upload {
my $service_name = shift;
shift;
my $version_num = shift;
my $type = shift;
my $name = shift;
shift;
shift;
my $file = shift || basename($name);
local($/);
open(my $fh, "<$file") || die "No file '$file'";
my $data = <$fh>;
close($fh);
eval {
my $service = $fastly->search_services(name => $service_name);
my $version = $service->version($version_num);
#XXX eats error
my $vcl = eval { $version->vcl($name) };
my $r;
if ($vcl) {
$vcl->content($data);
$r = $vcl->save;
} else {
$r = $version->upload_vcl($name, $data);
}
};
if ($@) {
print_error($@);
} else {
print "Uploaded $file as $name to service $service_name version $version_num\n";
}
return;
}
sub download {
my $service_name = shift;
shift;
my $version_num = shift;
my $type = shift;
my $name = shift;
shift;
my $tmp = shift; # so that people can do 'download service <id> version <num> vcl <name> to <file>' as well
my $file = shift || $tmp || $name;
my $dir = dirname($file);
$file = join "/", ($dir, basename($file, ".vcl").".vcl"); # ensure there's a .vcl ending
eval {
my $service = $fastly->search_services(name => $service_name);
my $version = $service->version($version_num);
my $vcl = eval { $version->vcl($name, include_content => 1) };
die "Couldn't find the vcl named $name" unless $vcl;
open(my $fh, ">$file") || die "Couldn't open the file '$file': $!";
print $fh $vcl->content;
close($fh);
};
if ($@) {
print_error($@);
} else {
print "Downloaded service $service_name version $version_num vcl $name to $file\n";
}
return;
}
sub delete {
my @args = @_;
my $name = shift @args;
my $service = eval { $fastly->search_services(name => $name) };
if ($@) {
print_error($@);
return;
}
if (!@args) {
my $r = eval { $fastly->delete_service($service) };
if (!$r) {
print BOLD, RED, "Error: ", RESET, "Couldn't delete service $name\n";
} else {
print "Deleted service ".$service->id."\n";
}
return;
}
if (@args < 2) {
print RED, "Need version", RESET, "\n";
return;
}
shift @args;
my $version = shift @args;
my $type = shift @args;
my $tname = shift @args;
my $meth = "delete_$type";
my $r = eval { $fastly->$meth({ service_id => $service->id, version => $version, name => $tname }) };
if ($@) {
print_error($@);
} else {
print "Deleted $type $tname\n";
}
return;
}
sub add {
my @args = @_;
my $service_name = $args[0];
my $version_num = $args[2];
my $dest_type = $args[6];
my $dest_name = $args[7];
my $src_type = $args[3];
my $src_name = $args[4];
my $service = eval { $fastly->search_services(name => $service_name ) };
my $src_meth = "get_${src_type}";
my $src = $fastly->$src_meth($service->id, $version_num, $src_name);
my $dest_meth = "get_${dest_type}";
my $dest = $fastly->$dest_meth($service->id, $version_num, $dest_name);
my $add_meth = "add_${src_type}";
my $out = eval { $dest->$add_meth($src) };
if ($@) {
print_error($@);
} else {
warn "Added $src_type ".$src->name." to $dest_type ".$dest->name." for service ".$service->name." v${version_num}\n";
}
}
sub create {
my @args = @_;
my $name = shift @args;
if (!@args) {
my $service = eval { $fastly->create_service(name => $name ) };
if ($@) {
print_error($@);
} else {
print "Created service ".$service->id."\n";
}
return;
}
if (@args < 2) {
print RED, "Need version", RESET, "\n";
return;
}
my $service = eval { $fastly->search_services(name => $name) };
#my $service_name = shift @args;
shift @args;
my $version = shift @args;
my $type = shift @args;
my $commands = $fastly->commands;
my $cmd = $commands->{"${type}.create"};
my %args;
if ($cmd->{name}) {
push @args, "name", shift @args
} else {
shift @args;
}
my $meth = "create_$type";
my $obj = eval { $fastly->$meth(service_id => $service->id, version => $version, @args) };
if ($@) {
print_error($@);
} else {
warn present({$obj->_as_hash});
}
}
sub list_objects {
no strict 'refs';
my $t = shift;
my $c = shift;
my @args = @{$c->{args}};
my $service_name = $args[0];
my $version_num = $args[2];
my $type = $args[3];
my ($service) = $fastly->search_services(name => $service_name, version => $version_num);
my $meth = "list_$type" . "s";
my @objects = $fastly->$meth( service_id => $service->id , version => $version_num);
return [sort map { $_->name } @objects];
}
sub model_args {
my $t = shift;
my $c = shift;
my $commands = $fastly->commands;
my $cmd = $commands->{"$c->{args}->[3].create"};
my @args = @{$c->{args}};
my %seen;
my $i = 0;
foreach my $arg (@args) {
$i++;
next if($i < 6);
$seen{$arg}++ if ($i % 2 == 0);
}
my $offset;
if (@args % 2 == 0 && $args[-1] && $cmd->{$args[-1]}) {
$offset = -1;
}
if (@args % 2 == 1) {
$offset = -2;
}
if ($offset) {
if ($cmd->{$args[$offset]}->{type} =~/heavenly::(.*)/) {
my $service = $fastly->search_services(name => $args[0], version => $args[2]);
my ($version) = $service->versions;
my $x = [$version->{$1} ];
return $x if @$x;
}
return sprintf(" (%s%s%s)", RED, $cmd->{$args[$offset]}->{desc}, RESET);
}
if ($c->{twice}) {
$t->completemsg("\n\n");
foreach my $c (sort {$a cmp $b} keys %$cmd) {
next if ($c eq 'name' || $c eq 'service' || $c eq 'version');
next if ($seen{$c});
if (exists $cmd->{$c}->{default}) {
$t->completemsg(sprintf("%s%-25s%s %s%s%s (%s)\n",BOLD,$c,RESET, BLUE,$cmd->{$c}->{desc},RESET, $cmd->{$c}->{default} || ""));
} else {
$t->completemsg(sprintf("%s%s%-25s%s %s%s%s\n",BOLD, RED,$c,RESET, BLUE,$cmd->{$c}->{desc},RESET));
}
}
}
my @r;
foreach my $c (sort {$a cmp $b} keys %$cmd) {
next if ($c eq 'name' || $c eq 'service' || $c eq 'version');
next if ($seen{$c});
push @r, $c;
}
push @r, "" if(@r == 1);
return \@r;
}
our %_mappings;
sub _mappings {
return %_mappings if keys %_mappings;
foreach my $class (@Net::Fastly::CLASSES) {
my $name = lc $class;
$name =~ s/^net::fastly:://;
$_mappings{$name} = $class;
}
%_mappings
}
sub service_models {
my $t = shift;
my $c = shift;
my %mapping = _mappings;
if ($@) {
my $string = "$@"; #copy becuase RED can reset it
$t->completemsg(sprintf("\n%s%s%s\n", RED, $string, RESET));
return [];
}
my @options;
foreach my $name (keys %_mappings) {
next if ($name eq 'service');
next if ($name eq 'version');
push @options, $name;
}
if (defined($c->{str}) && $c->{str} ne "") {
return [ grep { $_ =~ /^$c->{str}/} sort @options ];
}
return [sort @options];
}
sub show_service {
my $service_name = shift;
shift;
my $version_number = shift;
my ($service) = $fastly->search_services(name => $service_name, version => $version_number);
print present({$service->_as_hash});
}
sub show_versions {
my $service_name = shift;
my ($service) = $fastly->search_services(name => $service_name);
my @versions = $service->versions;
foreach my $version (@versions) {
print $version->number."\t\t".($version->updated_at || $version->created_at || "???")." ".($version->comment || "")."\n";
}
}
sub list_services {
$fastly->list_services();
}
sub show_services {
my @services = list_services;
foreach my $service (sort { $a->name cmp $b->name } @services) {
print $service->id."\t\t".$service->name."\n";
}
}
sub list_service_complete {
my $t = shift;
my $c = shift;
my @services = list_services;
if (defined($c->{str}) && $c->{str} ne "") {
my $match = lc($c->{str});
return [ grep { lc($_) =~ /^$match/ } sort map { $_->name } @services ];
} else {
return [ sort map { $_->name } @services ]
}
#my $substr = 0;
#if ($c->{str}) {
# $substr = index($c->{args}->[0], $c->{str});
#} else {
# $substr = $c->{tokoff};
#}
}
sub list_version {
my $t = shift;
my $c = shift;
my $service_name = $c->{args}->[0];
my ($service) = $fastly->search_services(name => $service_name);
return [sort {$a <=> $b} map { $_->number } $service->versions];
}
sub list_customers_complete {
my $t = shift;
my $c = shift;
my @customers = $fastly->list_customers;
if (defined($c->{str}) && $c->{str} ne "") {
my $match = lc($c->{str});
return [ grep { lc($_) =~ /^$match/ } sort map { $_->name } @customers ];
} else {
return [ sort map { $_->name } @customers ]
}
}
sub list_target {
my $t = shift;
my $c = shift;
my $service_name = $c->{args}->[0];
my $version_num = $c->{args}->[2];
my $type = $c->{args}->[6] || $c->{args}->[3];
my $version = _get_details($service_name, $version_num);
return [] unless $version;
my $objs = $version->{"${type}s"} || [];
return [ map { $_->{name} } @$objs ];
}
sub list_target_type {
my $t = shift;
my $c = shift;
my $service_name = $c->{args}->[0];
my $version_num = $c->{args}->[2];
my $type = $c->{args}->[3];
if ($type eq "backend") {
return ["director"];
} elsif ($type eq "director") {
return ["origin"];
} else {
return [];
}
}
sub present {
my $obj = shift;
Dump($obj);
}
sub _get_details {
my $service_name = shift;
my $version_num = shift;
my ($service) = $fastly->search_services(name => $service_name);
return undef unless $service;
my $details = $service->details(version => $version_num);
return $details->{version};
}
$term->commands($basecommands);
$term->run(@ARGV);
=head1 COMMANDS
=head2 help
Display a help message with available commands.
=head2 show
Display various thing. The sub commands are
=head3 services
List all the services you have access to with their ids and names.
fastly> show services
might show
KXKPV9svJFuPapAMjzxgP FooCorp
6g2rQokiwAGSRdGYhCY76v Example-Service
Y9puwhPNS5Y1tAjUbxp7Z Test
=head3 service
Display the information from one particular service including all
backends, directors, domains and origins.
fastly> show service <service name>
=head3 versions
Show the creation date of all the versions for a service.
fastly> show versions <service name>
=head3 diff
Display the diff between two different versions
fastly> show diff <service name> version <version number> to <version number>
=head3 stats
Display the stats for a service. Default last argument is minutely.
fastly> show stats <service name> [all|minutely|hourly|daily]
=head2 create
Create a new object.
fastly> create service <service name>
fastly> create service <service name> version <version number> backend <name> [options[s]]
fastly> create service <service name> version <version number> director <name> [options[s]]
fastly> create service <service name> version <version number> domain <name> [options[s]]
fastly> create service <service name> version <version number> origin <name> [options[s]]
Options look like
fastly> create service <service name> version <version number> backend <name> ipv4 <ip address>
=head2 set
Update an object. A note - you cannot change the names of things.
fastly> set service <service name> version <version number> backend <name> [options[s]]
fastly> set service <service name> version <version number> director <name> [options[s]]
fastly> set service <service name> version <version number> domain <name> [options[s]]
fastly> set service <service name> version <version number> origin <name> [options[s]]
Agagin, like create, options look like
fastly> set service <service name> version <version number> director <name> retries <retries>
=head2 delete
Delete an object from a configuration.
fastly> delete service <service name>
fastly> delete service <service name> version <version number>
fastly> delete service <service name> version <version number> backend <backend name>
fastly> delete service <service name> version <version number> director <director name>
fastly> delete service <service name> version <version number> domain <domain name>
fastly> delete service <service name> version <version number> origin <origin name>
=head2 clone
Clone a configuration so that the new version can be modified.
fastly> clone service <service name> version <version number>
=head2 activate
Activate a version for use - this will lock it and prevent any further modification.
fastly> activate service <service name> version <version number>
=head2 purge
Remove objects from the cache.
fastly> purge service <service name>
fastly> purge <url>
=head2 upload
Upload a custom VCL file
fastly> upload service <service name> version <version number> from <file> [as <vcl name>]
=head2 validate
Check to see that the currently uploaded VCL is valid.
fastly> validate service <service name> version <version number>
=head2 dump
Show the generated VCL for a given service.
fastly> dump service <service name> version <version number>
=head2 quit
Exit the Fastly shell.
=head1 COPYRIGHT
Copyright 2011 - Fastly Inc
Mail support at fastly dot com if you have problems.
=head1 DEVELOPERS
http://github.com/fastly/fastly-perl
http://www.fastly.com/documentation
=cut