[pve-devel] [PATCH pve-client 2/2] Add remote handling
Dietmar Maurer
dietmar at proxmox.com
Wed May 30 13:04:45 CEST 2018
Please can you try to split patches into smaller, functional independent parts?
> On May 30, 2018 at 12:47 PM René Jochum <r.jochum at proxmox.com> wrote:
>
>
> 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
>
> _______________________________________________
> pve-devel mailing list
> pve-devel at pve.proxmox.com
> https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
More information about the pve-devel
mailing list