#!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 = password = B 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= --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 --password --proxy http://localhost:8080 Lastly, the third method is to set your C 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 version vcl to ' 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 =head3 versions Show the creation date of all the versions for a service. fastly> show versions =head3 diff Display the diff between two different versions fastly> show diff version to =head3 stats Display the stats for a service. Default last argument is minutely. fastly> show stats [all|minutely|hourly|daily] =head2 create Create a new object. fastly> create service fastly> create service version backend [options[s]] fastly> create service version director [options[s]] fastly> create service version domain [options[s]] fastly> create service version origin [options[s]] Options look like fastly> create service version backend ipv4 =head2 set Update an object. A note - you cannot change the names of things. fastly> set service version backend [options[s]] fastly> set service version director [options[s]] fastly> set service version domain [options[s]] fastly> set service version origin [options[s]] Agagin, like create, options look like fastly> set service version director retries =head2 delete Delete an object from a configuration. fastly> delete service fastly> delete service version fastly> delete service version backend fastly> delete service version director fastly> delete service version domain fastly> delete service version origin =head2 clone Clone a configuration so that the new version can be modified. fastly> clone service version =head2 activate Activate a version for use - this will lock it and prevent any further modification. fastly> activate service version =head2 purge Remove objects from the cache. fastly> purge service fastly> purge =head2 upload Upload a custom VCL file fastly> upload service version from [as ] =head2 validate Check to see that the currently uploaded VCL is valid. fastly> validate service version =head2 dump Show the generated VCL for a given service. fastly> dump service version =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