[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