Skip to content

Instantly share code, notes, and snippets.

@xhanin
Created February 26, 2012 16:02
Show Gist options
  • Save xhanin/1917466 to your computer and use it in GitHub Desktop.
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>"
#!/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 );
}
@xhanin
Copy link
Author

xhanin commented Feb 26, 2012

Note that copy works only with text files. I may try to use scp instead of pure ssh, but it required less modifications...

@xhanin
Copy link
Author

xhanin commented Feb 26, 2012

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