[pve-devel] [PATCH v1 pve-common 08/18] pbsclient: document package and its public functions & methods

Max Carrara m.carrara at proxmox.com
Fri Aug 2 15:26:46 CEST 2024


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 at 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 at 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 at 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 at realm', # optional, defaults to 'root at 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 at realm', # optional, defaults to 'root at 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 at 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





More information about the pve-devel mailing list