use IO::File;
use JSON;
use MIME::Base64 qw(decode_base64);
-use POSIX qw(strftime ENOENT);
+use POSIX qw(mktime strftime ENOENT);
+use POSIX::strptime;
use PVE::APIClient::LWP;
use PVE::JSONSchema qw(get_standard_option);
return {
server => { fixed => 1 },
datastore => { fixed => 1 },
+ namespace => { optional => 1 },
port => { optional => 1 },
nodes => { optional => 1},
disable => { optional => 1},
'master-pubkey' => { optional => 1 },
maxfiles => { optional => 1 },
'prune-backups' => { optional => 1 },
+ 'max-protected-backups' => { optional => 1 },
fingerprint => { optional => 1 },
};
}
return "${storeid}:${volname}";
}
+my sub ns : prototype($$) {
+ my ($scfg, $name) = @_;
+ my $ns = $scfg->{namespace};
+ return defined($ns) ? ($name, $ns) : ();
+}
+
+# essentially the inverse of print_volid
+my sub api_param_from_volname : prototype($$$) {
+ my ($class, $scfg, $volname) = @_;
+
+ my $name = ($class->parse_volname($volname))[1];
+
+ my ($btype, $bid, $timestr) = split('/', $name);
+
+ my @tm = (POSIX::strptime($timestr, "%FT%TZ"));
+ # expect sec, min, hour, mday, mon, year
+ die "error parsing time from '$volname'" if grep { !defined($_) } @tm[0..5];
+
+ my $btime;
+ {
+ local $ENV{TZ} = 'UTC'; # $timestr is UTC
+
+ # Fill in isdst to avoid undef warning. No daylight saving time for UTC.
+ $tm[8] //= 0;
+
+ my $since_epoch = mktime(@tm) or die "error converting time from '$volname'\n";
+ $btime = int($since_epoch);
+ }
+
+ return {
+ (ns($scfg, 'ns')),
+ 'backup-type' => $btype,
+ 'backup-id' => $bid,
+ 'backup-time' => $btime,
+ };
+}
+
my $USE_CRYPT_PARAMS = {
backup => 1,
restore => 1,
push @$cmd, @$param if defined($param);
push @$cmd, "--repository", $repo;
+ if ($client_cmd ne 'status' && defined(my $ns = $scfg->{namespace})) {
+ push @$cmd, '--ns', $ns;
+ }
local $ENV{PBS_PASSWORD} = pbs_get_password($scfg, $storeid);
my $vmid = $backup->{'backup-id'};
my $volid = print_volid($storeid, $type, $vmid, $ctime);
+ my $mark = $backup->{keep} ? 'keep' : 'remove';
+ $mark = 'protected' if $backup->{protected};
+
push @{$prune_list}, {
ctime => $ctime,
- mark => $backup->{keep} ? 'keep' : 'remove',
+ mark => $mark,
type => $type eq 'vm' ? 'qemu' : 'lxc',
vmid => $vmid,
volid => $volid,
# artificial url - we currently do not use that anywhere
my $path = "pbs://$repo/$name";
+ if (defined(my $ns = $scfg->{namespace})) {
+ $ns =~ s|/|%2f|g; # other characters to escape aren't allowed in the namespace schema
+ $path .= "?ns=$ns";
+ }
return ($path, $vmid, $vtype);
}
content => 'backup',
vmid => int($bid),
ctime => $epoch,
+ subtype => $btype eq 'vm' ? 'qemu' : 'lxc', # convert to PVE backup type
};
$info->{verification} = $item->{verification} if defined($item->{verification});
$info->{notes} = $item->{comment} if defined($item->{comment});
+ $info->{protected} = 1 if $item->{protected};
if (defined($item->{fingerprint})) {
$info->{encrypted} = $item->{fingerprint};
} elsif (snapshot_files_encrypted($item->{files})) {
return 1;
}
+# FIXME remove on the next APIAGE reset.
+# Deprecated, use get_volume_attribute instead.
sub get_volume_notes {
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
return $data->{notes};
}
+# FIXME remove on the next APIAGE reset.
+# Deprecated, use update_volume_attribute instead.
sub update_volume_notes {
my ($class, $scfg, $storeid, $volname, $notes, $timeout) = @_;
return undef;
}
+sub get_volume_attribute {
+ my ($class, $scfg, $storeid, $volname, $attribute) = @_;
+
+ if ($attribute eq 'notes') {
+ return $class->get_volume_notes($scfg, $storeid, $volname);
+ }
+
+ if ($attribute eq 'protected') {
+ my $param = api_param_from_volname($class, $scfg, $volname);
+
+ my $password = pbs_get_password($scfg, $storeid);
+ my $conn = pbs_api_connect($scfg, $password);
+ my $datastore = $scfg->{datastore};
+
+ my $res = eval { $conn->get("/api2/json/admin/datastore/$datastore/$attribute", $param); };
+ if (my $err = $@) {
+ return if $err->{code} == 404; # not supported
+ die $err;
+ }
+ return $res;
+ }
+
+ return;
+}
+
+sub update_volume_attribute {
+ my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
+
+ if ($attribute eq 'notes') {
+ return $class->update_volume_notes($scfg, $storeid, $volname, $value);
+ }
+
+ if ($attribute eq 'protected') {
+ my $param = api_param_from_volname($class, $scfg, $volname);
+ $param->{$attribute} = $value;
+
+ my $password = pbs_get_password($scfg, $storeid);
+ my $conn = pbs_api_connect($scfg, $password);
+ my $datastore = $scfg->{datastore};
+
+ eval { $conn->put("/api2/json/admin/datastore/$datastore/$attribute", $param); };
+ if (my $err = $@) {
+ die "Server is not recent enough to support feature '$attribute'\n"
+ if $err->{code} == 404;
+ die $err;
+ }
+ return;
+ }
+
+ die "attribute '$attribute' is not supported for storage type '$scfg->{type}'\n";
+}
+
sub volume_size_info {
my ($class, $scfg, $storeid, $volname, $timeout) = @_;