use strict;
use warnings;
use JSON;
+use File::Basename qw(dirname);
+use File::Path qw(make_path);
-use File::HomeDir ();
-use PVE::JSONSchema qw(register_standard_option get_standard_option);
-use PVE::Tools qw(file_get_contents file_set_contents);
+use PVE::APIClient::Helpers;
+use PVE::APIClient::JSONSchema;
+use PVE::APIClient::SectionConfig;
+use PVE::APIClient::PTY;
+use PVE::APIClient::Tools qw(file_get_contents file_set_contents);
+
+use base qw(PVE::APIClient::SectionConfig);
+
+my $remote_namne_regex = qw(\w+);
+
+my $defaults_section = '!DEFAULTS';
my $complete_remote_name = sub {
- my $config = PVE::APIClient::Config->new();
- return $config->remote_names;
+ my $config = PVE::APIClient::Config->load();
+ my $list = [];
+ foreach my $name (keys %{$config->{ids}}) {
+ push @$list, $name if $name ne $defaults_section;
+ }
+ return $list;
};
-register_standard_option('pveclient-remote-name', {
+PVE::APIClient::JSONSchema::register_standard_option('pveclient-remote-name', {
description => "The name of the remote.",
type => 'string',
- pattern => qr(\w+),
+ pattern => $remote_namne_regex,
completion => $complete_remote_name,
});
-sub new {
- my ($class) = @_;
+my $defaultData = {
+ propertyList => {
+ type => {
+ description => "Section type.",
+ optional => 1,
+ },
+ },
+};
- my $self = {
- file => File::HomeDir::home() . '/.pveclient',
- };
- bless $self => $class;
+sub private {
+ return $defaultData;
+}
+
+sub config_filename {
+ my ($class) = @_;
- $self->load();
+ my $dir = PVE::APIClient::Helpers::configuration_directory();
- return $self;
+ return "$dir/config";
}
-sub load {
- my ($self) = @_;
+sub lock_config {
+ my ($class, $timeout, $code, @param) = @_;
- 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 $dir = PVE::APIClient::Helpers::configuration_directory();
+ make_path($dir);
+
+ my $filename = "$dir/.config.lck";
+
+ my $res = PVE::APIClient::Tools::lock_file($filename, $timeout, $code, @param);
+
+ die $@ if $@;
+
+ return $res;
+}
+
+sub format_section_header {
+ my ($class, $type, $sectionId, $scfg, $done_hash) = @_;
- my $contents = file_get_contents($self->{file});
- $self->{data} = from_json($contents);
+ if ($type eq 'defaults') {
+ return "defaults:\n";
} else {
- $self->{data} = {};
+ return "$type: $sectionId\n";
}
+}
+
+sub parse_section_header {
+ my ($class, $line) = @_;
- if (!exists($self->{data}->{remotes})) {
- $self->{data}->{remotes} = {};
+ if ($line =~ m/^defaults:\s*$/) {
+ return ('defaults', $defaults_section, undef, {});
+ } elsif ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
+ my ($type, $name) = (lc($1), $2);
+ eval {
+ die "invalid remote name '$name'\n"
+ if $name eq $defaults_section || $name !~ m/^$remote_namne_regex$/;
+ };
+ return ($type, $name, $@, {});
}
+ return undef;
+}
+
+sub load {
+ my ($class) = @_;
+
+ my $filename = $class->config_filename();
- # Verify config
- for my $name (@{$self->remote_names}) {
- my $cfg = $self->{data}->{remotes}->{$name};
+ my $raw = '';
- foreach my $opt (qw(host port username fingerprint)) {
- die "missing option '$opt' (remote '$name')" if !defined($cfg->{$opt});
+ if (-e $filename) {
+ my $filemode = (stat($filename))[2] & 07777;
+ if ($filemode != 0600) {
+ die sprintf "wrong permissions on '$filename' %04o (expected 0600)\n", $filemode;
}
+
+ $raw = file_get_contents($filename);
}
+
+ return $class->parse_config($filename, $raw);
}
sub save {
- my ($self) = @_;
+ my ($class, $cfg) = @_;
- my $contents = to_json($self->{data}, {pretty => 1, canonical => 1});
- file_set_contents($self->{file}, $contents, 0600);
-}
+ my $filename = $class->config_filename();
-sub add_remote {
- my ($self, $name, $host, $port, $fingerprint, $username, $password) = @_;
+ make_path(dirname($filename));
- $self->{data}->{remotes}->{$name} = {
- host => $host,
- port => $port,
- fingerprint => $fingerprint,
- username => $username,
- };
+ $cfg->{order}->{$defaults_section} = -1; # write as first section
+ my $raw = $class->write_config($filename, $cfg);
- if (defined($password)) {
- $self->{data}->{remotes}->{$name}->{password} = $password;
- }
+ file_set_contents($filename, $raw, 0600);
}
-sub remote_names {
- my ($self) = @_;
+sub get_defaults {
+ my ($class, $cfg) = @_;
- return [keys %{$self->{data}->{remotes}}];
+ $cfg->{ids}->{$defaults_section} //= {};
+
+ return $cfg->{ids}->{$defaults_section};
}
sub lookup_remote {
- my ($self, $name) = @_;
+ my ($class, $cfg, $name, $noerr) = @_;
+
+ my $data = $cfg->{ids}->{$name};
- die "Unknown remote \"$name\" given"
- if (!exists($self->{data}->{remotes}->{$name}));
+ return $data if $noerr || defined($data);
- return $self->{data}->{remotes}->{$name};
+ die "unknown remote \"$name\"\n";
}
-sub remotes {
- my ($self) = @_;
+sub remote_conn {
+ my ($class, $cfg, $remote) = @_;
+
+ my $section = $class->lookup_remote($cfg, $remote);
- my $res = {};
+ my $trylogin = sub {
+ my ($ticket_or_password) = @_;
- # Remove the password from each remote.
- for my $name ($self->remote_names) {
- my $cfg = $self->{data}->{remotes}->{$name};
- $res->{$name} = {
- host => $cfg->{host},
- port => $cfg->{port},
- username => $cfg->{username},
- fingerprint => $cfg->{fingerprint},
+ if (!defined($ticket_or_password)) {
+ $ticket_or_password = PVE::APIClient::PTY::read_password("Remote password: ")
+ }
+
+ my $setup = {
+ username => $section->{username},
+ password => $ticket_or_password,
+ host => $section->{host},
+ port => $section->{port} // 8006,
+ cached_fingerprints => {
+ $section->{fingerprint} => 1,
+ }
};
+
+ my $conn = PVE::APIClient::LWP->new(%$setup);
+
+ $conn->login();
+
+ return $conn;
+ };
+
+ my $password = $section->{password};
+
+ my $conn;
+
+ if (defined($password)) {
+ $conn = $trylogin->($password);
+ } else {
+
+ if (my $ticket = PVE::APIClient::Helpers::ticket_cache_lookup($remote)) {
+ eval { $conn = $trylogin->($ticket); };
+ if (my $err = $@) {
+ PVE::APIClient::Helpers::ticket_cache_update($remote, undef);
+ if (ref($err) && (ref($err) eq 'PVE::APIClient::Exception') && ($err->{code} == 401)) {
+ $conn = $trylogin->();
+ } else {
+ die $err;
+ }
+ }
+ } else {
+ $conn = $trylogin->();
+ }
}
- return $res;
+ PVE::APIClient::Helpers::ticket_cache_update($remote, $conn->{ticket});
+
+ return $conn;
}
-sub remove_remote {
- my ($self, $remote) = @_;
+package PVE::APIClient::RemoteConfig;
- $self->lookup_remote($remote);
+use strict;
+use warnings;
- delete($self->{data}->{remotes}->{$remote});
+use PVE::APIClient::JSONSchema qw(register_standard_option get_standard_option);
+use PVE::APIClient::SectionConfig;
- $self->save();
+use base qw( PVE::APIClient::Config);
+
+sub type {
+ return 'remote';
}
-sub remote_conn {
- my ($self, $remote) = @_;
-
- my $section = $self->lookup_remote($remote);
- my $conn = PVE::APIClient::LWP->new(
- username => $section->{username},
- password => $section->{password},
- host => $section->{host},
- port => $section->{port},
- cached_fingerprints => {
- $section->{fingerprint} => 1,
- }
- );
+sub properties {
+ return {
+ name => get_standard_option('pveclient-remote-name'),
+ host => {
+ description => "The host.",
+ type => 'string', format => 'address',
+ optional => 1,
+ },
+ username => {
+ description => "The username.",
+ type => 'string',
+ optional => 1,
+ },
+ password => {
+ description => "The users password.",
+ type => 'string',
+ optional => 1,
+ },
+ port => {
+ description => "The port.",
+ type => 'integer',
+ optional => 1,
+ default => 8006,
+ },
+ fingerprint => {
+ description => "Fingerprint.",
+ type => 'string',
+ optional => 1,
+ },
+ comment => {
+ description => "Description.",
+ type => 'string',
+ optional => 1,
+ maxLength => 4096,
+ },
+ };
+}
- $conn->login;
+sub options {
+ return {
+ name => { optional => 0 },
+ host => { optional => 0 },
+ comment => { optional => 1 },
+ username => { optional => 0 },
+ password => { optional => 1 },
+ port => { optional => 1 },
+ fingerprint => { optional => 1 },
+ };
+}
- return $conn;
+__PACKAGE__->register();
+
+
+package PVE::APIClient::DefaultsConfig;
+
+use strict;
+use warnings;
+
+use PVE::APIClient::JSONSchema qw(register_standard_option get_standard_option);
+
+use base qw( PVE::APIClient::Config);
+
+
+sub type {
+ return 'defaults';
}
+sub options {
+ return {
+ name => { optional => 1 },
+ username => { optional => 1 },
+ port => { optional => 1 },
+ };
+}
+
+__PACKAGE__->register();
+
+
+PVE::APIClient::Config->init();
+
1;