[pve-devel] [PATCH pve-client 2/2] Add remote handling
René Jochum
r.jochum at proxmox.com
Wed May 30 12:47:10 CEST 2018
Signed-off-by: René Jochum <r.jochum at proxmox.com>
---
PVE/APIClient/Commands/remote.pm | 118 ++++++++++++++++++++++----
PVE/APIClient/Config.pm | 174 ++++++++++++++++++++++++++++++++-------
PVE/APIClient/Helpers.pm | 9 ++
3 files changed, 254 insertions(+), 47 deletions(-)
diff --git a/PVE/APIClient/Commands/remote.pm b/PVE/APIClient/Commands/remote.pm
index a6eb512..51106a6 100644
--- a/PVE/APIClient/Commands/remote.pm
+++ b/PVE/APIClient/Commands/remote.pm
@@ -8,32 +8,72 @@ use PVE::APIClient::Config;
use PVE::CLIHandler;
-use base qw(PVE::CLIHandler);
-
-my $remote_name_regex = qr(\w+);
+use PVE::APIClient::Helpers ();
+use Term::ReadLine;
+use PVE::APIClient::LWP;
-my $complete_remote_name = sub {
+use Data::Dumper;
- my $conf = PVE::APIClient::Config::load_config();
+use base qw(PVE::CLIHandler);
- my $res = [];
+my $complete_remote_name = sub {
- foreach my $k (keys %$conf) {
- if ($k =~ m/^remote_($remote_name_regex)$/) {
- push @$res, $1;
- }
- }
+ my $config = PVE::APIClient::Config->new();
+ my $known_remotes = $config->remotes;
- return $res;
+ return [keys %{$known_remotes}];
};
register_standard_option('pveclient-remote-name', {
description => "The name of the remote.",
type => 'string',
- pattern => $remote_name_regex,
+ pattern => qr([\w\d\.\-\_]+),
completion => $complete_remote_name,
});
+sub read_password {
+ # return $ENV{PVE_PW_TICKET} if defined($ENV{PVE_PW_TICKET});
+
+ my $term = new Term::ReadLine ('pveclient');
+ my $attribs = $term->Attribs;
+ $attribs->{redisplay_function} = $attribs->{shadow_redisplay};
+ my $input = $term->readline('password: ');
+ my $conf = $term->readline('Retype new password: ');
+
+ # remove password from history
+ if ($term->Features->{autohistory}) {
+ my $historyPosition = $term->where_history();
+ $term->remove_history($historyPosition);
+ $term->remove_history($historyPosition - 1);
+ }
+
+ die "Passwords do not match.\n" if ($input ne $conf);
+ return $input;
+}
+
+__PACKAGE__->register_method ({
+ name => 'list',
+ path => 'list',
+ method => 'GET',
+ description => "List remotes from your config file.",
+ parameters => {
+ additionalProperties => 0,
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my $config = PVE::APIClient::Config->new();
+ my $known_remotes = $config->remotes;
+
+ printf("%10s %10s %10s %10s %100s\n", "Name", "Host", "Port", "Username", "Fingerprint");
+ for my $name ( keys %{ $known_remotes } ) {
+ my $remote = $known_remotes->{$name};
+ printf("%10s %10s %10s %10s %100s\n", $name, $remote->{'host'},
+ $remote->{'port'}, $remote->{'username'}, $remote->{'fingerprint'});
+ }
+
+ return undef;
+ }});
+
__PACKAGE__->register_method ({
name => 'add',
path => 'add',
@@ -50,7 +90,10 @@ __PACKAGE__->register_method ({
username => {
description => "The username.",
type => 'string',
- optional => 1,
+ },
+ password => {
+ description => "The users password",
+ type => 'string',
},
},
},
@@ -58,8 +101,39 @@ __PACKAGE__->register_method ({
code => sub {
my ($param) = @_;
- die "implement me";
+ my $port = 8006;
+ my $host = '';
+ if ($param->{host} =~ /${PVE::APIClient::Helpers::scheme_host_port_re}/) {
+ $port = $+{port} if (defined($+{port}));
+ $host = $+{host};
+ }
+
+ my $config = PVE::APIClient::Config->new();
+ my $known_remotes = $config->remotes;
+
+ if (exists($known_remotes->{$param->{name}})) {
+ die "Remote \"$param->{name}\" exists, remove it first\n";
+ }
+
+ my $last_fp = 0;
+ my $api = PVE::APIClient::LWP->new(
+ username => $param->{username},
+ password => $param->{password},
+ host => $host,
+ port => $port,
+ manual_verification => 1,
+ register_fingerprint_cb => sub {
+ my $fp = shift @_;
+ $last_fp = $fp;
+ },
+ );
+ $api->login();
+
+ $config->add_remote($param->{name}, $host, $port, $last_fp, $param->{username}, $param->{password});
+ $config->save;
+ print $last_fp . "\n";
+ return undef;
}});
__PACKAGE__->register_method ({
@@ -73,17 +147,25 @@ __PACKAGE__->register_method ({
name => get_standard_option('pveclient-remote-name'),
},
},
- returns => { type => 'null'},
+ returns => { type => 'string'},
code => sub {
my ($param) = @_;
- die "implement me";
+ my $config = PVE::APIClient::Config->new();
+ eval {
+ $config->remove_remote($param->{name});
+ }; die "Unknown remote \"$param->{name}\" given: $@\n" if $@;
+
+ $config->save;
+ print "OK";
+ return undef;
}});
our $cmddef = {
- add => [ __PACKAGE__, 'add', ['name', 'host']],
+ add => [ __PACKAGE__, 'add', ['name', 'host', 'username']],
remove => [ __PACKAGE__, 'remove', ['name']],
+ list => [__PACKAGE__, 'list'],
};
1;
diff --git a/PVE/APIClient/Config.pm b/PVE/APIClient/Config.pm
index e29a4fd..1870da7 100644
--- a/PVE/APIClient/Config.pm
+++ b/PVE/APIClient/Config.pm
@@ -2,52 +2,168 @@ package PVE::APIClient::Config;
use strict;
use warnings;
-use JSON;
-use File::HomeDir;
+require JSON;
-use PVE::Tools;
-use PVE::APIClient::LWP;
+use File::HomeDir qw(home);
+require File::Basename;
+require File::Path;
+use PVE::Tools qw(file_get_contents file_set_contents);
+use PVE::APIClient::Helpers ();
-sub load_config {
+sub new {
+ my $class = shift;
- my $filename = home() . '/.pveclient';
- my $conf_str = PVE::Tools::file_get_contents($filename);
+ my $self = {
+ cached_conns => {},
+ file => home() . '/.config/pve-client/config.json',
+ };
+ bless $self => $class;
- my $filemode = (stat($filename))[2] & 07777;
- if ($filemode != 0600) {
- die sprintf "wrong permissions on '$filename' %04o (expected 0600)\n", $filemode;
+ return $self;
+}
+
+sub load {
+ my ($self, $reload) = @_;
+
+ if (!$reload && defined($self->{data})) {
+ return;
+ }
+
+ if (-e $self->{file}) {
+ my $filemode = (stat($self->{file}))[2] & 07777;
+ if ($filemode != 0600) {
+ die sprintf "wrong permissions on '$self->{file}' %04o (expected 0600)\n", $filemode;
+ }
+
+ my $contents = file_get_contents($self->{file});
+ $self->{data} = JSON::from_json($contents);
+ } else {
+ $self->{data} = {};
+ }
+
+ if (!exists($self->{data}->{remotes})) {
+ $self->{data}->{remotes} = {};
+ }
+}
+
+sub save {
+ my ($self) = @_;
+
+ $self->load();
+
+ my $dir = File::Basename::dirname( $self->{file} );
+ if (! -d $dir) {
+ File::Path::make_path($dir, { mode => 0700 });
}
- return decode_json($conf_str);
+ my $contents = JSON::to_json($self->{data}, {pretty => 1, canonical => 1});
+ file_set_contents($self->{file}, $contents, 0600);
+}
+
+sub add_remote {
+ my ($self, $name, $host, $port, $fingerprint, $username, $password) = @_;
+
+ $self->load();
+
+ $self->{data}->{remotes}->{$name} = {
+ host => $host,
+ port => $port,
+ fingerprint => $fingerprint,
+ username => $username,
+ };
+
+ if (defined($password)) {
+ $self->{data}->{remotes}->{$name}->{password} = $password;
+ }
}
-sub load_remote_config {
- my ($remote) = @_;
+sub remotes {
+ my ($self) = @_;
- my $conf = load_config();
+ $self->load();
- my $remote_conf = $conf->{"remote_$remote"} ||
- die "no such remote '$remote'\n";
+ my $res = {};
- foreach my $opt (qw(hostname username password fingerprint)) {
- die "missing option '$opt' (remote '$remote')" if !defined($remote_conf->{$opt});
+ # Remove the password from each remote.
+ for my $name ( keys %{ $self->{data}->{remotes} } ) {
+ my $cfg = $self->{data}->{remotes}->{$name};
+ $res->{$name} = {
+ host => $cfg->{host},
+ port => $cfg->{port},
+ username => $cfg->{username},
+ fingerprint => $cfg->{fingerprint},
+ };
}
- return $remote_conf;
+ return $res;
+}
+
+sub remove_remote {
+ my ( $self, $remote ) = @_;
+
+ $self->load();
+
+ my $remote_name = "";
+ if ( $remote =~ /${PVE::APIClient::Helpers::remote_cmd_re}/ ) {
+ $remote_name = $+{remote_name};
+ }
+ else {
+ die "Not a remote name: $remote";
+ }
+
+ die "Unknown remote \"$remote\" given"
+ if (!exists($self->{data}->{remotes}->{$remote_name}));
+
+ delete($self->{data}->{remotes}->{$remote_name});
+
+ $self->save();
}
-sub get_remote_connection {
- my ($remote) = @_;
+sub remote_conn {
+ my ( $self, $remote, %params ) = @_;
+
+ my $remote_name = "";
+ if ( $remote =~ /${PVE::APIClient::Helpers::remote_cmd_re}/ ) {
+ $remote_name = $+{remote_name};
+ }
+ else {
+ die "Not a remote name: \"$remote\"";
+ }
+
+ die "Unknown remote \"$remote_name\" given"
+ if (!exists($self->{data}->{remotes}->{$remote_name}));
+
+ my $section = $self->{data}->{remotes}->{$remote_name};
+
+ $params{username} = $section->{username};
+ $params{password} = $section->{password};
+ $params{host} = $section->{host};
+ $params{port} = $section->{port} if ( defined( $section->{port} ) );
+ $params{cached_fingerprints} = {
+ $section->{fingerprint} => 1,
+ };
+
+ my $ssl_default_opts = { verify_hostname => 0 };
+ my $ssl_opts = $params{ssl_opts} || $ssl_default_opts;
+
+ my $conn = PVE::APIClient::LWP->new(
+ username => $params{username},
+ password => $params{password},
+ host => $params{host} || 'localhost',
+ port => $params{port},
+ protocol => $params{protocol},
+ cookie_name => $params{cookie_name} // 'PVEAuthCookie',
+ manual_verification => $params{manual_verification},
+ cached_fingerprints => $params{cached_fingerprints} || {},
+ verify_fingerprint_cb => $params{verify_fingerprint_cb},
+ register_fingerprint_cb => $params{register_fingerprint_cb},
+ ssl_opts => $ssl_opts,
+ timeout => $params{timeout} || 60,
+ );
- my $conf = load_remote_config($remote);
+ $conn->login;
- return PVE::APIClient::LWP->new(
- username => $conf->{username},
- password => $conf->{password},
- host => $conf->{hostname},
- cached_fingerprints => {
- $conf->{fingerprint} => 1
- });
+ return $conn;
}
1;
diff --git a/PVE/APIClient/Helpers.pm b/PVE/APIClient/Helpers.pm
index e7f2216..2b1e18d 100644
--- a/PVE/APIClient/Helpers.pm
+++ b/PVE/APIClient/Helpers.pm
@@ -10,6 +10,15 @@ use Encode::Locale;
use Encode;
use HTTP::Status qw(:constants);
+our $remote_re = qr/(?<remote_name>[\w\d\.\-\_]+)/;
+our $remote_cmd_re = qr/^\s*$remote_re:?/;
+my $vmid_re = qr/(?<vmid>[\d]+)/;
+
+our $remote_vmid_cmd_re =
+ qr/^\s*$remote_re:$vmid_re\s*$/;
+
+our $scheme_host_port_re = qr/(?<scheme>[a-z][a-z0-9+\-.]*:\/\/)?(?<host>[a-z0-9\-._~%]+|\[[a-z0-9\-._~%!\$&'()*+,;=:]+\]):?(?<port>[0-9]+)?/;
+
my $pve_api_definition;
my $pve_api_path_hash;
--
2.11.0
More information about the pve-devel
mailing list