[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