Created
February 26, 2012 16:02
-
-
Save xhanin/1917466 to your computer and use it in GitHub Desktop.
slightly updated version of OVH public cloud script, allowing to ssh exec with "./ovhcloud instance ssh <instanceid> <command>" and remote copy with "./ovhcloud instance ssh <instanceid> copy <from> <to>"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
use strict; | |
my $VERSION='v0.9'; | |
############# | |
# PREPARING # | |
############# | |
my $URLS = { | |
session => | |
{ | |
schema => "https://ws.ovh.com/sessionHandler/r1/schema.json", | |
rest => "https://ws.ovh.com/sessionHandler/r1/rest.dispatcher" | |
}, | |
instance => | |
{ | |
schema => "https://ws.ovh.com/cloud/public/trunk/schema.json", | |
rest => "https://ws.ovh.com/cloud/public/trunk/rest.dispatcher" | |
}, | |
storage => | |
{ | |
schema => "https://ws.ovh.com/cloud/public/storage/trunk/schema.json", | |
rest => "https://ws.ovh.com/cloud/public/storage/trunk/rest.dispatcher" | |
} | |
}; | |
my @MODULES = qw/ | |
JSON | |
LWP | |
LWP::Protocol::https | |
Term::ReadLine | |
Term::ReadKey | |
Text::Table | |
Data::Dumper | |
Getopt::Long | |
Net::SCP::Expect | |
/; | |
my $VERBOSE_LVL = 0; | |
my $NO_SKIP = 0; | |
my $LOGGER = Logger->new(logLevel => $VERBOSE_LVL); | |
foreach my $module (@MODULES) | |
{ | |
eval "use $module"; | |
if($@) | |
{ | |
installModule($module); | |
} | |
else | |
{ | |
$LOGGER->debug("Loaded module: $module."); | |
} | |
}; | |
my $MAX_RETRY = 3; | |
my $SESSION_DEFS = getDEFSFromJson(url => $URLS->{session}->{schema}); | |
my $NS = shift @ARGV; | |
my $FUNCTION = shift @ARGV; | |
my ($DEFS, $URL, @AVAILABLE_FN); | |
if($NS eq "instance") | |
{ | |
$DEFS = getDEFSFromJson(url => $URLS->{instance}->{schema}); | |
$URL = $URLS->{instance}->{rest}; | |
} | |
elsif($NS eq "storage") | |
{ | |
$DEFS = getDEFSFromJson(url => $URLS->{storage}->{schema}); | |
$URL = $URLS->{storage}->{rest}; | |
} | |
elsif($NS eq '') | |
{ | |
$LOGGER->critical("Specify namespace"); | |
_help(); | |
exit 1; | |
} | |
else | |
{ | |
$LOGGER->critical("Unknown namespace '$NS'"); | |
_help(); | |
exit 1; | |
} | |
my @optsConf; | |
generateOpts(defs => $DEFS, opts => \@optsConf); | |
generateOpts(defs => $SESSION_DEFS, opts => \@optsConf); | |
push @optsConf, "v+"; | |
push @optsConf, "noskip|n"; | |
my %OPTS; | |
Getopt::Long::Configure ("bundling"); | |
GetOptions (\%OPTS, @optsConf); | |
$VERBOSE_LVL = $OPTS{v}; | |
$NO_SKIP = $OPTS{noskip} || $OPTS{n}; | |
$LOGGER->{logLevel} = $VERBOSE_LVL if $VERBOSE_LVL; | |
if($DEFS) | |
{ | |
$LOGGER->debug("Got $NS definitions."); | |
} | |
else | |
{ | |
$LOGGER->critical("No $NS definitions."); | |
exit 1; | |
} | |
unless($FUNCTION) | |
{ | |
$LOGGER->critical("Function not specified."); | |
_help(); | |
exit 0; | |
} | |
foreach my $funcName (keys %{$SESSION_DEFS->{functions}}) | |
{ | |
push @AVAILABLE_FN, $funcName; | |
} | |
foreach my $funcName (keys %{$DEFS->{functions}}) | |
{ | |
push @AVAILABLE_FN, $funcName; | |
} | |
@AVAILABLE_FN = sort @AVAILABLE_FN; | |
if($FUNCTION eq 'help') | |
{ | |
$LOGGER->debug("Calling help function."); | |
callHelp(argv => \@ARGV ); | |
exit 0; | |
} | |
if($FUNCTION eq 'ssh') | |
{ | |
$LOGGER->debug("Calling ssh function."); | |
ssh(shift @ARGV); | |
exit 0; | |
} | |
if(!grep({$_ eq $FUNCTION} @AVAILABLE_FN)) | |
{ | |
$LOGGER->critical("Function '$FUNCTION' not found in namespace $NS.\n"); | |
exit 1; | |
} | |
######## | |
# MAIN # | |
######## | |
$LOGGER->debug("Getting params for $FUNCTION"); | |
my $params = getParams(function => $FUNCTION); | |
$LOGGER->debug("Calling function $FUNCTION"); | |
my $fnret = callFunction(function => $FUNCTION, params => $params); | |
$LOGGER->debug("Got data."); | |
print "Function returned:\n"; | |
formatOutput(data => $fnret->{answer}); | |
exit 0; | |
############### | |
# SUBROUTINES # | |
############### | |
sub generateOpts | |
{ | |
my %params = @_; | |
my $defs = $params{defs}; | |
my $opts = $params{opts}; | |
foreach my $func (keys %{$defs->{functions}}) | |
{ | |
my $params = $defs->{functions}->{$func}; | |
foreach my $param (keys %{$params->{parameters}}) | |
{ | |
if($params->{parameters}->{$param}->{type} eq 'long') | |
{ | |
push @$opts, "$param=i" unless grep {$_ eq "$param=i"} @$opts; | |
} | |
elsif($params->{parameters}->{$param}->{type} eq 'string') | |
{ | |
push @$opts, "$param=s" unless grep {$_ eq "$param=s"} @$opts; | |
} | |
} | |
} | |
} | |
sub getDEFSFromJson | |
{ | |
my %params = @_; | |
$LOGGER->debug("Called fucntion getDEFSFromJson with params: " . Dumper(\%params)); | |
my $url = $params{url}; | |
my $browser = LWP::UserAgent->new; | |
my $response = $browser->get($url); | |
if(!$response->is_success()) | |
{ | |
$LOGGER->critical("Couldn't get $url."); | |
$LOGGER->debug(Dumper($response)); | |
exit 1; | |
} | |
my $data = decode_json($response->content); | |
return $data; | |
} | |
sub callFunction | |
{ | |
my %params = @_; | |
$LOGGER->debug("Called fucntion callFunction with params: " . Dumper(\%params)); | |
my $session; | |
my $gateway; | |
my $retry = 0; | |
$params{params} = {} unless $params{params}; | |
my $def = $SESSION_DEFS->{functions}->{$params{function}}; | |
if( $def and %$def) | |
{ | |
$gateway = $URLS->{session}->{rest}; | |
} | |
elsif(%{$DEFS->{functions}->{$params{function}}}) | |
{ | |
$gateway = $URL; | |
} | |
my $json = JSON->new(); | |
my $browser = LWP::UserAgent->new; | |
my $data = $json->encode($params{params}); | |
my $url = $gateway."/$params{function}"; | |
$LOGGER->debug("session: ".Dumper($session)."params: ".Dumper($data)); | |
$session = getSession() if $params{function} ne 'login'; | |
START: | |
my $fnret = $browser->post($url, [ session => $session, params => $data ] ); | |
if(!$fnret->is_success()) | |
{ | |
$LOGGER->critical("Couldn't post $url."); | |
$LOGGER->debug(Dumper($fnret)); | |
exit 1; | |
} | |
my $response = $json->decode($fnret->content); | |
if($response->{error}) | |
{ | |
$LOGGER->debug("callFunction\n: ".Dumper(\%params).Dumper($response)); | |
$LOGGER->critical("ERROR: $response->{error}->{status}, $response->{error}->{message}"); | |
if($response->{error}->{status} == 301 ) | |
{ | |
$session=login(); | |
if(++$retry < $MAX_RETRY) | |
{ | |
goto START; | |
} | |
} | |
else | |
{ | |
exit 1; | |
} | |
}; | |
$LOGGER->debug("response: " . Dumper($fnret)); | |
return $response; | |
} | |
sub storeSession | |
{ | |
my $sessionId = shift; | |
$LOGGER->debug("Called fucntion storeSession with params: sessionId = '$sessionId'."); | |
my $dir = $ENV{HOME}.'/.ovh'; | |
if(! -d $dir ) | |
{ | |
mkdir $dir; | |
chmod 0700, $dir; | |
} | |
if(open(FILE, ">$dir/session")) | |
{ | |
$LOGGER->debug("Storing sessionId: $sessionId\n"); | |
print FILE $sessionId; | |
close(FILE); | |
} | |
else | |
{ | |
$LOGGER->debug("Cannot save session in $dir/session: $!\n"); | |
} | |
} | |
sub getSession | |
{ | |
$LOGGER->debug("Called fucntion getSession."); | |
my $sessionId = ''; | |
if(open(FILE, $ENV{HOME}.'/.ovh/session' )) | |
{ | |
$sessionId = <FILE>; | |
chomp($sessionId); | |
close(FILE); | |
$LOGGER->warn("getting sessionId: $sessionId\n"); | |
} | |
return $sessionId; | |
} | |
sub getParams | |
{ | |
my %params = @_; | |
$LOGGER->debug("Called function getParams with params: " . Dumper(\%params)); | |
my $inputParams = $SESSION_DEFS->{functions}->{$params{function}}->{parameters} || | |
$DEFS->{functions}->{$params{function}}->{parameters}; | |
my $fnparams; | |
foreach my $param (sort keys %$inputParams) | |
{ | |
next unless $param; | |
next if($param =~ /sessionId/); | |
my $type = $inputParams->{$param}->{"\$ref"} || $inputParams->{$param}->{type}; | |
if(!$NO_SKIP) | |
{ | |
unless($inputParams->{$param}->{required}) | |
{ | |
if($VERBOSE_LVL) | |
{ | |
print "$param ($type) [not required]: skipped\n" unless $param =~ /sessionId/; | |
} | |
next unless $OPTS{$param} | |
} | |
} | |
if(not exists $inputParams->{$param}->{"\$ref"}) | |
{ | |
if($param =~ /password/i ) | |
{ | |
print "$param ($type): " unless $param =~ /sessionId/; | |
#hide typing password | |
ReadMode('noecho'); | |
$fnparams->{$param} = ReadLine(0); | |
ReadMode('restore'); | |
print "\n"; | |
} | |
else | |
{ | |
if($OPTS{$param}) | |
{ | |
$fnparams->{$param} = $OPTS{$param}; | |
$LOGGER->debug("Got $param param from CMD line: $OPTS{$param}\n"); | |
print "$param ($type): " unless $param =~ /sessionId/; | |
print $OPTS{$param} . "\n"; | |
next; | |
} | |
if($param =~ /(.+)Name/ or $param =~ /(.+)Id/) | |
{ | |
my $f = "get" . ucfirst $1 . "s"; | |
$LOGGER->debug("Calling $f for available options"); | |
my $params = getParams(function => $f); | |
my $fnret = callFunction(function => $f, params => $params); | |
print "Available options:\n"; | |
formatOutput( data => $fnret->{answer}); | |
}; | |
print "$param ($type): " unless $param =~ /sessionId/; | |
$fnparams->{$param} = <STDIN>; | |
} | |
chomp($fnparams->{$param}); | |
} | |
else | |
{ | |
print "$param ($type): " unless $param =~ /sessionId/; | |
$fnparams->{$param} = getStruct($inputParams->{$param}->{"\$ref"},1); | |
} | |
} | |
$LOGGER->debug("done\n"); | |
return $fnparams; | |
} | |
sub getStruct | |
{ | |
my $type = shift; | |
my $indent = shift || 0; | |
my $argv = shift; | |
$LOGGER->debug("Called function getStruct with params: type = '$type', indent = '$indent'."); | |
my $properties = $SESSION_DEFS->{data}->{$type}->{properties} || | |
$DEFS->{data}->{$type}->{properties}; | |
my $struct = {}; | |
print "\n"; | |
foreach my $property (sort keys %$properties) | |
{ | |
my $propType = $properties->{$property}->{"\$ref"} || $properties->{$property}->{type}; | |
if(!$NO_SKIP) | |
{ | |
unless($properties->{$property}->{required}) | |
{ | |
if($VERBOSE_LVL) | |
{ | |
print (" "x$indent); | |
print "$property ($propType) [not required]: skipped\n"; | |
}; | |
next; | |
} | |
} | |
print (" "x$indent); | |
print "$property ($propType): "; | |
if($properties->{$property}->{"\$ref"}) | |
{ | |
$struct->{$property} = getStruct($properties->{$property}->{"\$ref"},$indent+1,$argv); | |
} | |
else | |
{ | |
if($OPTS{$property}) | |
{ | |
$struct->{$property} = $OPTS{$property}; | |
$LOGGER->debug("Got $property param from CMD line: $OPTS{$property}\n"); | |
print $OPTS{$property}."\n"; | |
next; | |
} | |
$struct->{$property} = <STDIN>; | |
chomp($struct->{$property}); | |
} | |
} | |
return $struct; | |
} | |
sub login | |
{ | |
$LOGGER->debug("Called function login."); | |
my $function = 'login'; | |
my $params = getParams(function => $function); | |
$LOGGER->debug("Calling function $function with params\n"); | |
my $fnret = callFunction(function => $function, params => $params); | |
if($fnret->{error}) | |
{ | |
$LOGGER->critical("ERROR: $fnret->{error}->{status}, $fnret->{error}->{message}\n"); | |
exit 1; | |
} | |
else | |
{ | |
storeSession($fnret->{answer}->{id}); | |
} | |
return $fnret->{answer}->{id}; | |
} | |
sub ssh | |
{ | |
$LOGGER->debug("Called function ssh with params: " . Dumper(\@_)); | |
my $id = shift; | |
unless($id) | |
{ | |
$LOGGER->critical("No instanceId specified."); | |
$LOGGER->debug("Getting params for getInstances"); | |
my $params = getParams(function => 'getInstances'); | |
$LOGGER->debug("Calling function getInstances"); | |
my $fnret = callFunction(function => 'getInstances', params => $params); | |
formatOutput(data => $fnret->{answer}); | |
print "instanceId (long): "; | |
$id = <STDIN>; | |
} | |
$LOGGER->debug("Calling function getInstance in ssh."); | |
my $fnret = callFunction(function => 'getInstance', params => {instanceId => $id}); | |
if($fnret->{error}) | |
{ | |
$LOGGER->critical("ERROR: $fnret->{error}->{status}, $fnret->{error}->{message}\n"); | |
exit 1; | |
} | |
if($fnret->{answer}->{status} ne 'running') | |
{ | |
$LOGGER->critical("Instance not running."); | |
exit 1; | |
} | |
my $ip = $fnret->{answer}->{ipv4}; | |
$LOGGER->debug("Calling function getLoginInformations in ssh.\n"); | |
my $fnret = callFunction(function => 'getLoginInformations', params => {instanceId => $id}); | |
if($fnret->{error}) | |
{ | |
$LOGGER->critical("ERROR: $fnret->{error}->{status}, $fnret->{error}->{message}\n"); | |
exit 1; | |
} | |
my $login = $fnret->{answer}->{login}; | |
my $password = $fnret->{answer}->{password}; | |
foreach my $module (qw/Expect/) | |
{ | |
eval "require $module"; | |
if($@) | |
{ | |
installModule($module); | |
} | |
else | |
{ | |
$LOGGER->debug("Loaded module: $module."); | |
} | |
} | |
if ($ARGV[0] eq "copy") | |
{ | |
$LOGGER->debug("Using copy mode."); | |
shift @ARGV; | |
my $f = shift @ARGV; | |
my $t = shift @ARGV; | |
my $scpe = Net::SCP::Expect->new(host=>$ip, user=>$login, password=>$password); | |
$scpe->scp($f,$t); | |
} | |
else | |
{ | |
my $expect = new Expect; | |
# broken on my box when instanceid is not read from stdin | |
# $expect->slave->clone_winsize_from(\*STDIN); | |
my $spawn = $expect->spawn("ssh $login\@$ip @ARGV") or $LOGGER->critical("Cannot spawn ssh."); | |
#fix for resizing windows | |
$SIG{WINCH} = \&winch; | |
sub winch | |
{ | |
$expect->slave->clone_winsize_from(\*STDIN); | |
kill WINCH => $expect->pid if $expect->pid; | |
$SIG{WINCH} = \&winch; | |
} | |
$spawn->expect(10, | |
[ 'Are you sure you want to continue connecting' => sub{$spawn->send("yes\n"); Expect::exp_continue()}], | |
[ 'password: $' => sub {$spawn->send($password . "\n"); Expect::exp_continue();}], | |
[ '~' => sub{$spawn->interact()}] | |
); | |
} | |
} | |
sub formatOutput | |
{ | |
my %params = @_; | |
$LOGGER->debug("Called formatOutput login with params: " . Dumper(\%params)); | |
my $data = $params{data}; | |
if(defined $data) | |
{ | |
my @head; | |
if(ref($data) eq "ARRAY") | |
{ | |
@head = grep !/__/, keys %{$data->[0]}; | |
} | |
else | |
{ | |
@head = grep !/__/, keys %{$data}; | |
$data = [ $data ]; | |
}; | |
my @datas; | |
foreach my $opt (@{$data}) | |
{ | |
my @row; | |
foreach my $col (@head) | |
{ | |
if(ref $opt->{$col} eq "ARRAY") | |
{ | |
my $value = $opt->{$col}->[0]->{id} || ""; | |
push @row, $value; | |
} | |
elsif(ref $opt->{$col} eq "HASH") | |
{ | |
my $value = $opt->{$col}->{name} || ""; | |
push @row, $value; | |
} | |
else | |
{ | |
push @row, $opt->{$col}; | |
} | |
} | |
push @datas, \@row; | |
} | |
my @underlinedHead; | |
foreach my $head (@head) | |
{ | |
push @underlinedHead, $head . "\n" . "-" x length($head); | |
} | |
my $table = Text::Table->new(@underlinedHead); | |
$table->load(@datas); | |
print $table,"\n"; | |
} | |
} | |
sub callHelp | |
{ | |
my %params = @_; | |
my $argv = $params{argv}; | |
$LOGGER->debug("Called callHelp function with params: " . Dumper(\%params)); | |
my @functions = (); | |
if(defined($argv) and ref($argv) eq 'ARRAY' and scalar(@$argv)) | |
{ | |
foreach my $name (@$argv) | |
{ | |
foreach my $func (grep(/$name/, @AVAILABLE_FN)) | |
{ | |
push @functions, $func; | |
} | |
} | |
} | |
else | |
{ | |
@functions = @AVAILABLE_FN; | |
} | |
my ($name, $type, $description, $required); | |
foreach my $functionName (@functions) | |
{ | |
my $fn = $SESSION_DEFS->{functions}->{$functionName} || | |
$DEFS->{functions}->{$functionName}; | |
print "-"x80 . "\n"; | |
print "name: $functionName\n"; | |
print "description: $fn->{description}\n"; | |
print "parameters:\n\n"; | |
my $table = Text::Table->new("Name\n----","Type\n----","Required?\n---------","Description\n-----------"); | |
foreach my $param (sort keys %{$fn->{parameters}}) | |
{ | |
$name = $param; | |
$type = $fn->{parameters}->{$param}->{type} || $fn->{parameters}->{$param}->{"\$ref"}; | |
$required = "required" if $fn->{parameters}->{$param}->{required}; | |
$description = $fn->{parameters}->{$param}->{description}; | |
$table->add($name,$type,$required,$description); | |
} | |
print $table,"\n"; | |
} | |
} | |
sub installModule | |
{ | |
my $module = shift; | |
$LOGGER->critical("Cannot load module: $module."); | |
print "Would you like to install $module now? (yes/no): "; | |
chomp(my $response = <STDIN>); | |
if($response eq "yes") | |
{ | |
unless(system("apt-get > /dev/null") >> 8) | |
{ | |
$LOGGER->debug("Installing $module with apt-get."); | |
my $package = lc $module; | |
$package =~ s/::/-/g; | |
$package = "lib".$package."-perl"; | |
my $command = "apt-get install $package"; | |
$command = "sudo " . $command if $>; | |
if(my $code = system($command) >> 8) | |
{ | |
$LOGGER->critical("Installing $module with apt-get failed (exit code: $code)."); | |
} | |
else | |
{ | |
eval "use $module"; | |
return; | |
} | |
} | |
$LOGGER->debug("Installing $module with cpan."); | |
my $command = "cpan -iff $module"; | |
$command = "sudo " . $command if $>; | |
if(my $code = system($command) >> 8) | |
{ | |
$LOGGER->critical("Installing $module failed with cpan (exit code: $code)."); | |
exit 1; | |
} | |
eval "use $module"; | |
} | |
else | |
{ | |
exit 1; | |
} | |
}; | |
sub _help | |
{ | |
$LOGGER->debug("Called _help function."); | |
print STDERR <<END | |
ovhcloud $VERSION | |
USAGE: | |
call function: | |
$0 [instance|storage] functionName [[-v+] [--noskip|-n] [--parameterName parameter] ... | |
ssh to instance: | |
$0 instance ssh [instanceId] | |
get help for function: | |
$0 [instance|storage] help [-v+] [functionName|regex] | |
verbosity levels: | |
-v - warn | |
-vv - debug | |
don't skip optional parameters: | |
--noskip, -n | |
END | |
} | |
package Logger; | |
use strict; | |
use constant Logger_critical => 0; | |
use constant Logger_warn => 1; | |
use constant Logger_debug => 2; | |
sub new | |
{ | |
my $type = shift; | |
my %params = @_; | |
my $class = ref( $type) || $type || "Logger"; | |
my $this = { | |
logLevel => $params{logLevel} | |
}; | |
bless $this, $class; | |
} | |
sub LOG | |
{ | |
my $this = shift; | |
my %params = @_; | |
if($this->{logLevel} >= $params{level} ) | |
{ | |
print STDERR $params{text} . "\n"; | |
} | |
} | |
sub debug | |
{ | |
my $this = shift; | |
my $text = shift; | |
$this->LOG(level => Logger_debug, 'text' => $text ); | |
} | |
sub warn | |
{ | |
my $this = shift; | |
my $text = shift; | |
$this->LOG(level => Logger_warn, 'text' => $text ); | |
} | |
sub critical | |
{ | |
my $this = shift; | |
my $text = shift; | |
$this->LOG(level => Logger_critical, 'text' => $text ); | |
} |
Updated to use perl Net:SCP to upload files
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that copy works only with text files. I may try to use scp instead of pure ssh, but it required less modifications...