From: Max Carrara <m.carrara@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH v1 pve-common 08/18] pbsclient: document package and its public functions & methods
Date: Fri, 2 Aug 2024 15:26:46 +0200 [thread overview]
Message-ID: <20240802132656.270077-9-m.carrara@proxmox.com> (raw)
In-Reply-To: <20240802132656.270077-1-m.carrara@proxmox.com>
This commit adds a brief overview for the `PVE::PBSClient` package and
documents its public functions and methods. Examples are added where
deemed appropriate.
Signed-off-by: Max Carrara <m.carrara@proxmox.com>
---
src/PVE/PBSClient.pm | 526 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 511 insertions(+), 15 deletions(-)
diff --git a/src/PVE/PBSClient.pm b/src/PVE/PBSClient.pm
index 231406a..e0468d3 100644
--- a/src/PVE/PBSClient.pm
+++ b/src/PVE/PBSClient.pm
@@ -1,5 +1,4 @@
package PVE::PBSClient;
-# utility functions for interaction with Proxmox Backup client CLI executable
use strict;
use warnings;
@@ -13,14 +12,83 @@ use POSIX qw(mkfifo strftime ENOENT);
use PVE::JSONSchema qw(get_standard_option);
use PVE::Tools qw(run_command file_set_contents file_get_contents file_read_firstline $IPV6RE);
-# returns a repository string suitable for proxmox-backup-client, pbs-restore, etc.
-# $scfg must have the following structure:
-# {
-# datastore
-# server
-# port (optional defaults to 8007)
-# username (optional defaults to 'root@pam')
-# }
+=pod
+
+=head1 NAME
+
+PVE::PBSClient - Proxmox Backup Client Library
+
+=head1 DESCRIPTION
+
+This package contains utilities that wrap common Proxmox Backup client CLI
+operations.
+
+=head2 THE CLIENT OBJECT
+
+While the C<L<PVE::PBSClient>> package contains regular L<functions|/FUNCTIONS>,
+the majority is done via the C<L<PVE::PBSClient>> object. This object represents
+a client that is used to connect to a Proxmox Backup Server:
+
+ use strict;
+ use warnings;
+
+ use Data::Dumper;
+
+ $Data::Dumper::Indent = 1;
+ $Data::Dumper::Quotekeys = 0;
+ $Data::Dumper::Sortkeys = 1;
+ $Data::Dumper::Terse = 1;
+
+ use PVE::PBSClient;
+
+ my $scfg = {
+ server => 'example.tld',
+ username => 'alice@pam',
+ datastore => 'foo-store',
+ fingerprint => '...',
+ };
+
+ my $client = PVE::PBSClient->new($scfg, "pbs-main");
+
+ my ($total, $free, $used) = $client->status();
+
+ print "Datastore has a total capacity of $total bytes, of which $used bytes are used"
+ . "and $free bytes are still available.\n";
+
+ my $snapshot = "vm/1337/2024-07-30T10:01:57Z";
+ my $filepath = "/";
+
+ my $file_list = $client->file_restore_list($snapshot, $filepath);
+
+ print "The snapshot '$snapshot' contains the following restorable files:\n";
+ print Dumper($file_list);
+ print "\n";
+
+
+=head1 FUNCTIONS
+
+=cut
+
+=pod
+
+=head3 get_repository
+
+ $repository = get_repository($scfg)
+
+Returns a repository string suitable for the C<proxmox-backup-client> and
+C<proxmox-file-restore> executables.
+
+The C<$scfg> hash must have the following structure:
+
+ {
+ datastore => 'my-datastore-name',
+ server => 'example.tld',
+ port => 8007, # optional, defaults to 8007
+ username => 'user@realm', # optional, defaults to 'root@pam'
+ }
+
+=cut
+
sub get_repository {
my ($scfg) = @_;
@@ -41,6 +109,58 @@ sub get_repository {
return "$username\@$server:$datastore";
}
+=pod
+
+=head1 METHODS
+
+=cut
+
+=pod
+
+=head3 new
+
+ $client = PVE::PBSClient->new($scfg, $storeid)
+ $client = PVE::PBSClient->new($scfg, $storeid, $secret_dir)
+
+Creates a new instance of a C<L<PVE::PBSClient>>.
+
+Throws an exception if no C<$scfg> hash is provided or if C<$storeid> is C<undef>.
+
+=over
+
+=item C<$scfg>
+
+The I<storage config> hash that the client should use.
+
+This hash is expected to have the following structure:
+
+ {
+ datastore => 'my-datastore-name',
+ namespace => 'my-namespace',
+ server => 'example.tld',
+ fingerprint => '...',
+ port => 8007, # optional, defaults to 8007
+ username => 'user@realm', # optional, defaults to 'root@pam'
+ }
+
+=item C<$storeid>
+
+The I<ID> of the storage corresponding to C<$scfg>. This ID is used for operations
+concerning the I<password> and I<encryption key>, such as C<L</get_password>> and
+C<L</set_encryption_key>>.
+
+=item C<$secret_dir> (optional)
+
+The name of the I<secret directory> in which the I<password> and I<encryption key>
+files are stored. Defaults to C</etc/pve/priv/storage>.
+
+Note that the I<password> and I<encryption key> files are expected to be named
+C<foo.pw> and C<foo.enc> respectively, if, for example, C<$storeid> is C<"foo">.
+
+=back
+
+=cut
+
sub new {
my ($class, $scfg, $storeid, $secret_dir) = @_;
@@ -63,6 +183,21 @@ my sub password_file_name {
return "$self->{secret_dir}/$self->{storeid}.pw";
}
+=pod
+
+=head3 set_password
+
+ $client->set_password($password)
+
+Updates or creates the I<password> file, storing the given C<$password>.
+
+If the I<secret directory> does not exist, it is created beforehand.
+
+If the I<password> file does not exist, a new one with the permissions C<600>
+is created.
+
+=cut
+
sub set_password {
my ($self, $password) = @_;
@@ -72,6 +207,19 @@ sub set_password {
PVE::Tools::file_set_contents($pwfile, "$password\n", 0600);
};
+=pod
+
+=head3 delete_password
+
+ $client->delete_password()
+
+Deletes the I<password> file inside the I<secret directory>.
+
+Will throw an exception if deleting the I<password> file fails, but not
+if the file doesn't exist.
+
+=cut
+
sub delete_password {
my ($self) = @_;
@@ -83,6 +231,16 @@ sub delete_password {
}
};
+=pod
+
+=head3 get_password
+
+ $password = $client->get_password()
+
+Reads and returns the I<password> from its file inside the I<secret directory>.
+
+=cut
+
sub get_password {
my ($self) = @_;
@@ -91,12 +249,38 @@ sub get_password {
return PVE::Tools::file_read_firstline($pwfile);
}
+=pod
+
+=head3 encryption_key_file_name
+
+ $file_name = $self->encryption_key_file_name()
+
+Returns the full name of the I<encryption key> file, including the path of the
+I<secret directory> it is located in.
+
+=cut
+
sub encryption_key_file_name {
my ($self) = @_;
return "$self->{secret_dir}/$self->{storeid}.enc";
};
+=pod
+
+=head3 set_encryption_key
+
+ $client->set_encryption_key($key)
+
+Updates or creates the I<encryption key> file, storing the given C<$key>.
+
+If the I<secret directory> does not exist, it is created beforehand.
+
+If the I<encryption key> file does not exist, a new one with the permissions C<600>
+is created.
+
+=cut
+
sub set_encryption_key {
my ($self, $key) = @_;
@@ -106,6 +290,19 @@ sub set_encryption_key {
PVE::Tools::file_set_contents($encfile, "$key\n", 0600);
};
+=pod
+
+=head3 delete_encryption_key
+
+ $client->delete_encryption_key()
+
+Deletes the I<encryption key> file inside the I<secret directory>.
+
+Will throw an exception if deleting the I<encryption key> file fails, but not
+if the file doesn't exist.
+
+=cut
+
sub delete_encryption_key {
my ($self) = @_;
@@ -235,6 +432,21 @@ my sub run_client_cmd : prototype($$;$$$$) {
return $res;
}
+=pod
+
+=head3 autogen_encryption_key
+
+ $new_key = $client->autogen_encryption_key()
+
+Generates a new I<encryption key> and stores it as a file inside the I<secret directory>.
+The raw contents of the key file, B<which are encoded as JSON string>, are
+returned afterwards.
+
+If an I<encryption key> file already exists at its expected location, an
+exception is thrown.
+
+=cut
+
sub autogen_encryption_key {
my ($self) = @_;
my $encfile = $self->encryption_key_file_name();
@@ -257,7 +469,61 @@ my sub split_namespaced_parameter : prototype($$) {
return ($namespace, $snapshot);
}
-# lists all snapshots, optionally limited to a specific group
+=pod
+
+=head3 get_snapshots
+
+ $snapshots = $client->get_snapshots()
+ $snapshots = $client->get_snapshots($group)
+
+Returns all snapshots of the current client instance as a list of nesteded hashes.
+
+Optionally, the snapshots may be filtered by their C<$group>, such as C<"vm/100">
+or C<"ct/2000">, for example.
+
+The returned list has the following structure:
+
+ [
+ {
+ 'backup-id' => "100",
+ 'backup-time' => 1721901601,
+ 'backup-type' => "vm",
+ comment => "standalone node/example-host/100-example-vm",
+ files: [
+ {
+ 'crypt-mode' => "encrypt",
+ filename => "qemu-server.conf.blob",
+ size => 428
+ },
+ {
+ 'crypt-mode' => "encrypt",
+ filename => "drive-scsi0.img.fidx",
+ size => 17179869184
+ },
+ {
+ 'crypt-mode' => "sign-only",
+ filename => "index.json.blob",
+ size => 651
+ },
+ {
+ filename => "client.log.blob"
+ },
+ ...
+ ],
+ fingerprint => "...",
+ owner => "root@pam",
+ protected => false,
+ size => 17179870263,
+ verification => {
+ state => "ok",
+ upid => "..."
+ }
+ },
+ ...
+ ]
+
+=cut
+
sub get_snapshots {
my ($self, $group) = @_;
@@ -274,8 +540,28 @@ sub get_snapshots {
return run_client_cmd($self, "snapshots", $param, undef, undef, $namespace);
};
-# create a new PXAR backup of a FS directory tree - doesn't cross FS boundary
-# by default.
+=pod
+
+=head3 backup_fs_tree
+
+ $client->backup_fs_tree($root, $id, $pxarname)
+ $client->backup_fs_tree($root, $id, $pxarname, $cmd_opts)
+
+Create a new PXAR backup of a directory tree starting at C<$root>.
+
+C<$id> is the I<ID> of the stored backup and C<$pxarname> is the name of the
+uploaded PXAR archive.
+
+Optionally, the C<$cmd_opts> hash may be supplied, which should contain
+additional parameters to pass to C<L<PVE::Tools::run_command>> that is used
+under the hood.
+
+Raises an exception if either C<$root>, C<$id> or C<$pxarname> is C<undef>.
+
+B<NOTE:> This does B<not> cross filesystem boundaries.
+
+=cut
+
sub backup_fs_tree {
my ($self, $root, $id, $pxarname, $cmd_opts) = @_;
@@ -298,6 +584,32 @@ sub backup_fs_tree {
return run_raw_client_cmd($self, 'backup', $param, %$cmd_opts);
};
+=pod
+
+=head3 restore_pxar
+
+ $client->restore_pxar($snapshot, $pxarname, $target)
+ $client->restore_pxar($snapshot, $pxarname, $target, $cmd_opts)
+
+Restore a PXAR backup of a directory tree from the given C<$snapshot>.
+
+C<$pxarname> is the name of the previously uploaded PXAR archive to restore and
+C<$target> the directory to which the backed up tree will be restored to.
+
+Note that C<$snapshot> must be the snapshot's complete name in the format
+C<TYPE/ID/BACKUP_TIME> - for example C<"vm/100/2023-07-31T16:00:00Z"> or
+C<"ct/2000/2024-08-01T09:54:08Z"> (like it's displayed in the PBS UI).
+
+Optionally, the C<$cmd_opts> hash may be supplied, which should contain
+additional parameters to pass to C<L<PVE::Tools::run_command>> that is used
+under the hood.
+
+Raises an exception if either C<$snapshot>, C<$pxarname> or C<$target> is C<undef>,
+or if a filesystem entry to be restored already exists inside the C<$target>
+directory.
+
+=cut
+
sub restore_pxar {
my ($self, $snapshot, $pxarname, $target, $cmd_opts) = @_;
@@ -320,6 +632,22 @@ sub restore_pxar {
return run_raw_client_cmd($self, 'restore', $param, %$cmd_opts);
};
+=pod
+
+=head3 forget_snapshot
+
+ $client->forget_snapshot($snapshot)
+
+Forgets the given C<$snapshot>.
+
+Note that C<$snapshot> must be the snapshot's complete name in the format
+C<TYPE/ID/BACKUP_TIME> - for example C<"vm/100/2023-07-31T16:00:00Z"> or
+C<"ct/2000/2024-08-01T09:54:08Z"> (as displayed in the PBS UI).
+
+Raises an exception if C<$snapshot> is C<undef>.
+
+=cut
+
sub forget_snapshot {
my ($self, $snapshot) = @_;
@@ -330,6 +658,41 @@ sub forget_snapshot {
return run_client_cmd($self, 'forget', [ "$snapshot" ], 1, undef, $namespace)
};
+=pod
+
+=head3 prune_group
+
+ $client->prune_group($opts, $prune_opts, $group)
+
+Prunes a backup C<$group>. The exact behaviour can be controlled using the
+C<$opts> and C<$prune_opts> hashes.
+
+C<$group> must be in the format of C<TYPE/ID>, like C<"vm/100"> or C<"ct/2000">,
+for example (as displayed in the PBS UI).
+
+The C<$opts> hash supports the following options and may be left empty:
+
+ {
+ 'dry-run' => 1, # perform a dry run
+ }
+
+The C<$prune_opts> hash supports the following options:
+
+ {
+ 'keep-last' => 1,
+ 'keep-hourly' => 1,
+ 'keep-daily' => 1,
+ 'keep-weekly' => 1,
+ 'keep-monthly' => 1,
+ 'keep-yearly' => 1,
+ }
+
+Will do nothing if no C<$prune_opts> are supplied.
+
+Raises an exception if C<$group> is C<undef>.
+
+=cut
+
sub prune_group {
my ($self, $opts, $prune_opts, $group) = @_;
@@ -356,6 +719,19 @@ sub prune_group {
return run_client_cmd($self, 'prune', $param, undef, undef, $namespace);
};
+=pod
+
+=head3 status
+
+ ($total, $free, $used, $active) = $client->status()
+
+Return the I<status> of the client's repository as an array.
+
+The array contains the C<$total>, C<$free> and C<$used> size of the repository
+as bytes, as well as whether the repository is C<$active> or not.
+
+=cut
+
sub status {
my ($self) = @_;
@@ -379,6 +755,59 @@ sub status {
return ($total, $free, $used, $active);
};
+=pod
+
+=head3 file_restore_list
+
+ $restore_list = $client->($snapshot, $filepath)
+ $restore_list = $client->($snapshot, $filepath, $base64)
+ $restore_list = $client->($snapshot, $filepath, $base64, $extra_params)
+
+Return the list of entries from a directory C<$filepath> of a backup C<$snapshot>.
+
+Note that C<$snapshot> must be the snapshot's complete name in the format
+C<TYPE/ID/BACKUP_TIME> - for example C<"vm/100/2023-07-31T16:00:00Z"> or
+C<"ct/2000/2024-08-01T09:54:08Z"> (as displayed in the PBS UI).
+
+C<$base64> may optionally be set to C<1> if the C<$filepath> is base64-encoded.
+
+The C<$extra_params> hash supports the following options and may be left empty:
+
+ {
+ timeout => 5, # in seconds
+ }
+
+If successful, the returned list of hashes has the following structure:
+
+ [
+ {
+ filepath => "L2RyaXZlLXNjc2kwLmltZy5maWR4",
+ leaf => 0,
+ size => 34359738368,
+ text => "drive-scsi0.img.fidx",
+ type => "v"
+ },
+ {
+ filepath => "L2RyaXZlLXNjc2kxLmltZy5maWR4",
+ leaf => 0,
+ size => 68719476736,
+ text => "drive-scsi1.img.fidx",
+ type => "v"
+ },
+ ...
+ ]
+
+On error, the list of hashes will contain an error message, for example:
+
+ [
+ {
+ message => "wrong key - unable to verify signature since manifest's key [...] does not match provided key [...]"
+ },
+ ...
+ ]
+
+=cut
+
sub file_restore_list {
my ($self, $snapshot, $filepath, $base64, $extra_params) = @_;
@@ -399,8 +828,32 @@ sub file_restore_list {
);
}
-# call sync from API, returns a fifo path for streaming data to clients,
-# pass it to file_restore_extract to start transfering data
+=pod
+
+=head3 file_restore_extract_prepare
+
+ $fifo = $client->file_restore_extract_prepare()
+
+Create a I<named pipe> (FIFO) for streaming data and return its path.
+
+The returned path is usually passed to C<L</file_restore_extract>> which will
+stream the data to the I<named pipe>. A different process may then read from the
+same path, receiving the streamed data.
+
+Raises an exception if:
+
+=over
+
+=item creating the I<named pipe> fails
+
+=item the call to C<getpwnam> for the C<www-data> user fails
+
+=item changing the permissions for the I<named pipe> or its directory fails
+
+=back
+
+=cut
+
sub file_restore_extract_prepare {
my ($self) = @_;
@@ -419,7 +872,50 @@ sub file_restore_extract_prepare {
return "$tmpdir/fifo";
}
-# this blocks while data is transfered, call this from a background worker
+=pod
+
+=head3 file_restore_extract
+
+ $client->file_restore_extract($output_file, $snapshot, $filepath, $base64, $tar)
+
+Restores and extracts a C<$filepath> from a C<$snapshot> to the given C<$output_file>.
+By default, the C<$output_file> will be a ZIP archive.
+
+Because this method is mostly used for streaming purposes in conjunction with
+C<L</file_restore_extract_prepare>>, the C<$output_file> will be automatically
+I<unlinked> once the extraction is complete. See below for an example on how to
+use this method.
+
+C<$base64> may optionally be set to C<1> if the C<$filepath> is base64-encoded.
+
+C<$tar> may optionally be set to C<1> if the output written to C<$output_file>
+should be a ZSTD-compressed TAR archive. B<Otherwise, the file will be saved as
+a ZIP archive.>
+
+Usually used in conjunction with C<L</file_restore_extract_prepare>>.
+
+B<NOTE:> This method B<blocks> while data is being transferred to C<$output_file>.
+It is therefore best to call this within C<L<PVE::RESTEnvironment::fork_worker>>
+or C<L<PVE::RPCEnvironment::fork_worker>>, for example:
+
+ # [...]
+ my $fifo = $client->file_restore_extract_prepare();
+
+ $rpcenv->fork_worker('backup-download', undef, $user, sub {
+ print "Starting download of file: $filename\n";
+ $client->file_restore_extract($fifo, $snapshot, $filepath, 0, $tar);
+ });
+
+ return {
+ download => {
+ path => $fifo,
+ stream => 1,
+ 'content-type' => 'application/octet-stream',
+ },
+ };
+
+=cut
+
sub file_restore_extract {
my ($self, $output_file, $snapshot, $filepath, $base64, $tar) = @_;
--
2.39.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2024-08-02 13:28 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-08-02 13:26 [pve-devel] [PATCH v1 pve-common 00/18] Introduction of libproxmox-backup-client-perl Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 01/18] pbsclient: rename 'sdir' parameter of constructor to 'secret_dir' Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 02/18] pbsclient: use parentheses when calling most inbuilts Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 03/18] pbsclient: use post-if definedness checks instead of '//=' operator Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 04/18] pbsclient: pull variable out of long post-if definedness check Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 05/18] pbsclient: use cond. statements instead of chained 'or' operators Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 06/18] pbsclient: use spaces around list braces and parens around ternaries Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 07/18] pbsclient: s/foreach/for Max Carrara
2024-08-02 13:26 ` Max Carrara [this message]
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 09/18] pbsclient: create secret dir with `mkdir -p` and mode `700` Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 10/18] pbsclient: use `File::Spec->catfile` to concatenate file paths Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 11/18] pbsclient: let `status` method return a hash instead of an array Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 12/18] pbsclient: throw exception if username of client has no realm Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 13/18] pbsclient: make method `password_file_name` public Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 14/18] pbsclient: prohibit implicit return Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 15/18] pbsclient: don't return anything in PXAR methods Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 16/18] pbsclient: don't return anything in `forget_snapshot` Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 17/18] make: support building multiple packages from the same source Max Carrara
2024-08-02 13:26 ` [pve-devel] [PATCH v1 pve-common 18/18] deb: split PBSClient.pm into new package libproxmox-backup-client-perl Max Carrara
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240802132656.270077-9-m.carrara@proxmox.com \
--to=m.carrara@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox