* [pve-devel] [PATCH pve-storage/pve-manager 0/3] allow download of compressed ISOs @ 2023-07-25 14:37 Philipp Hufnagl 2023-07-25 14:37 ` [pve-devel] [PATCH pve-storage 1/2] fix #4849: download-url: allow download and decompression " Philipp Hufnagl ` (2 more replies) 0 siblings, 3 replies; 7+ messages in thread From: Philipp Hufnagl @ 2023-07-25 14:37 UTC (permalink / raw) To: pve-devel Many web pages offer the download of the disk images compressed. This patch allows the download of archives (like .gz), automatically detects the format and decompresses it pve-storage: Philipp Hufnagl (2): fix #4849: download-url: allow download and decompression of compressed ISOs clean: fix whitspaces and minor code issues src/PVE/API2/Storage/Status.pm | 38 ++++++++++++----- src/PVE/Storage.pm | 77 +++++++++++++++++++++------------- src/PVE/Storage/Plugin.pm | 3 +- 3 files changed, 77 insertions(+), 41 deletions(-) pve-manager: Philipp Hufnagl (1): fix #4849: download to storage: automatically dectect and configure compression PVE/API2/Nodes.pm | 21 ++++++++++++++++- www/manager6/Makefile | 1 + www/manager6/form/DecompressionSelector.js | 14 +++++++++++ www/manager6/window/DownloadUrlToStorage.js | 26 +++++++++++++++++++-- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 www/manager6/form/DecompressionSelector.js -- 2.39.2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [pve-devel] [PATCH pve-storage 1/2] fix #4849: download-url: allow download and decompression of compressed ISOs 2023-07-25 14:37 [pve-devel] [PATCH pve-storage/pve-manager 0/3] allow download of compressed ISOs Philipp Hufnagl @ 2023-07-25 14:37 ` Philipp Hufnagl 2023-07-26 12:31 ` Fabian Grünbichler 2023-07-25 14:37 ` [pve-devel] [PATCH pve-storage 2/2] clean: fix whitspaces and minor code issues Philipp Hufnagl 2023-07-25 14:37 ` [pve-devel] [PATCH pve-manager 1/1] fix #4849: download to storage: automatically detect and configure compression Philipp Hufnagl 2 siblings, 1 reply; 7+ messages in thread From: Philipp Hufnagl @ 2023-07-25 14:37 UTC (permalink / raw) To: pve-devel Signed-off-by: Philipp Hufnagl <p.hufnagl@proxmox.com> --- src/PVE/API2/Storage/Status.pm | 20 ++++++++++++++++++-- src/PVE/Storage.pm | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.pm index e4ce698..9ac4660 100644 --- a/src/PVE/API2/Storage/Status.pm +++ b/src/PVE/API2/Storage/Status.pm @@ -578,6 +578,11 @@ __PACKAGE__->register_method({ requires => 'checksum-algorithm', optional => 1, }, + compression => { + description => "The compression algorithm used to compress", + type => 'string', + optional => 1, + }, 'checksum-algorithm' => { description => "The algorithm to calculate the checksum of the file.", type => 'string', @@ -642,14 +647,25 @@ __PACKAGE__->register_method({ http_proxy => $dccfg->{http_proxy}, }; - my ($checksum, $checksum_algorithm) = $param->@{'checksum', 'checksum-algorithm'}; + my ($checksum, $checksum_algorithm, $compression) = $param->@{'checksum', 'checksum-algorithm', 'compression'}; if ($checksum) { $opts->{"${checksum_algorithm}sum"} = $checksum; $opts->{hash_required} = 1; } my $worker = sub { - PVE::Tools::download_file_from_url("$path/$filename", $url, $opts); + my $save_to = "$path/$filename"; + die "refusing to override existing file $save_to \n" if -e $save_to ; + $save_to .= ".$compression" if $compression; + PVE::Tools::download_file_from_url($save_to, $url, $opts); + if($compression) + { + my $decrypton_error = PVE::Storage::decompress_iso($compression, $save_to); + print $decrypton_error if $decrypton_error; + unlink $save_to; + + + } }; my $worker_id = PVE::Tools::encode_text($filename); # must not pass : or the like as w-ID diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm index b99ed35..0c62cc8 100755 --- a/src/PVE/Storage.pm +++ b/src/PVE/Storage.pm @@ -1532,6 +1532,11 @@ sub decompressor_info { lzo => ['lzop', '-d', '-c'], zst => ['zstd', '-q', '-d', '-c'], }, + iso => { + gz => ['zstd', '-q', '-d'], + zst => ['zstd', '-q', '-d'], + lzo => ['lzop', '-q', '-d'], + }, }; die "ERROR: archive format not defined\n" @@ -1611,6 +1616,23 @@ sub archive_auxiliaries_remove { } } +sub decompress_iso +{ + my ($compression, $file) = @_; + + my $raw = ''; + my $out = sub { + my $output = shift; + $raw .= "$output\n"; + }; + + my $info = decompressor_info('iso', $compression); + my $decompressor = $info->{decompressor}; + + run_command([@$decompressor, $file], outfunc => $out); + return wantarray ? ($raw, undef) : $raw; +} + sub extract_vzdump_config_tar { my ($archive, $conf_re) = @_; -- 2.39.2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [pve-devel] [PATCH pve-storage 1/2] fix #4849: download-url: allow download and decompression of compressed ISOs 2023-07-25 14:37 ` [pve-devel] [PATCH pve-storage 1/2] fix #4849: download-url: allow download and decompression " Philipp Hufnagl @ 2023-07-26 12:31 ` Fabian Grünbichler 0 siblings, 0 replies; 7+ messages in thread From: Fabian Grünbichler @ 2023-07-26 12:31 UTC (permalink / raw) To: Proxmox VE development discussion On July 25, 2023 4:37 pm, Philipp Hufnagl wrote: > Signed-off-by: Philipp Hufnagl <p.hufnagl@proxmox.com> > --- > src/PVE/API2/Storage/Status.pm | 20 ++++++++++++++++++-- > src/PVE/Storage.pm | 22 ++++++++++++++++++++++ > 2 files changed, 40 insertions(+), 2 deletions(-) > > diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.pm > index e4ce698..9ac4660 100644 > --- a/src/PVE/API2/Storage/Status.pm > +++ b/src/PVE/API2/Storage/Status.pm > @@ -578,6 +578,11 @@ __PACKAGE__->register_method({ > requires => 'checksum-algorithm', > optional => 1, > }, > + compression => { > + description => "The compression algorithm used to compress", > + type => 'string', should probably be restricted via an enum schema (containing 'zstd', 'gz' and 'lzop' for now). > + optional => 1, > + }, nit: I would at least the change the description to indicate that setting this means the downloaded file will be decompressed using this algorithm > 'checksum-algorithm' => { > description => "The algorithm to calculate the checksum of the file.", > type => 'string', > @@ -642,14 +647,25 @@ __PACKAGE__->register_method({ > http_proxy => $dccfg->{http_proxy}, > }; > > - my ($checksum, $checksum_algorithm) = $param->@{'checksum', 'checksum-algorithm'}; > + my ($checksum, $checksum_algorithm, $compression) = $param->@{'checksum', 'checksum-algorithm', 'compression'}; > if ($checksum) { > $opts->{"${checksum_algorithm}sum"} = $checksum; > $opts->{hash_required} = 1; > } compression should only be allowed for isos (for now), since templates are *always* compressed at the moment, and uncompressing them while downloading would actually make them unusable AFAICT? > > my $worker = sub { > - PVE::Tools::download_file_from_url("$path/$filename", $url, $opts); > + my $save_to = "$path/$filename"; > + die "refusing to override existing file $save_to \n" if -e $save_to ; > + $save_to .= ".$compression" if $compression; > + PVE::Tools::download_file_from_url($save_to, $url, $opts); > + if($compression) > + { > + my $decrypton_error = PVE::Storage::decompress_iso($compression, $save_to); > + print $decrypton_error if $decrypton_error; > + unlink $save_to; > + > + > + } the decompression here could (or probably should) be moved into download_file_from_url (passing in the decompression command via $opts). this would also make the handling of the tmpfile easier, since now we have - a tempfile for the download - renamed to a tempfile for the decompression - uncompressed to final destination - intermediate tempfile needs manual cleanup if the decompression is moved into the helper (including any required cleanups), we are not concerned at all about the tmpfiles here, which would be nice(r) IMHO. other than that, there is a few things that could be improved: - code style (positioning of {, blank lines) - $decrypton -> there is no encryption happening here :) - error handling: -- errors are normally not returned, but passed up the stack via "die" -- the error should be propagated up so that the task fails and the user knows the download failed and why, see below for more details > }; > > my $worker_id = PVE::Tools::encode_text($filename); # must not pass : or the like as w-ID > diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm > index b99ed35..0c62cc8 100755 > --- a/src/PVE/Storage.pm > +++ b/src/PVE/Storage.pm > @@ -1532,6 +1532,11 @@ sub decompressor_info { > lzo => ['lzop', '-d', '-c'], > zst => ['zstd', '-q', '-d', '-c'], > }, > + iso => { > + gz => ['zstd', '-q', '-d'], this might warrant a comment ;) > + zst => ['zstd', '-q', '-d'], > + lzo => ['lzop', '-q', '-d'], > + }, we did discuss this a bit already, but also posting here on list in case someone else has an opinion: while we possibly lose a bit of performance (although I doubt it really matters much for regular iso files), aligning the iso and vma commands here would simplify the filename handling - if the returned command simply decompresses the next pushed argument to stdout, we don't have to account for the peculiarities of each command with regards to automatic extension removal. we could also save writing the data to disk twice (compressed, and then uncompressed) if we add calculating the digest to the pipe, or at least do that for the non-verifying case. isos are commonly stored on NFS/CIFS shares, where this is even more expensive cause of the network round trips. > }; > > die "ERROR: archive format not defined\n" > @@ -1611,6 +1616,23 @@ sub archive_auxiliaries_remove { > } > } > > +sub decompress_iso > +{ nit: style again > + my ($compression, $file) = @_; > + > + my $raw = ''; > + my $out = sub { > + my $output = shift; > + $raw .= "$output\n"; > + }; > + > + my $info = decompressor_info('iso', $compression); > + my $decompressor = $info->{decompressor}; > + > + run_command([@$decompressor, $file], outfunc => $out); outfunc only captures STDOUT, not STDERR, so I don't think this part does what you want it to do (based on the naming of the '$decrypton_error' variable above).. there also is a 'logfunc' (capturing both) and an 'errfunc' (capturing only STDERR). also, not all output is necessarily an error, even in the face of `-q`. I think it should be safe to assume that any decompression tool we call here will fail run_command if the decompression fails for whatever reason, in which case decompress_iso here will die, but that is *not* handled at its call site, so no cleanup will happen and the compressed, downloaded file will remain... note that by default, run_command will just forward the command's output (on both FDs) to whatever those point to outside, so a plain run_command should do the right thing in most circumstances, and outfunc and friends are only needed if you actually need to do something (parse, filter, ..) with the output. > + return wantarray ? ($raw, undef) : $raw; why? we sometimes use wantarray, but only if it actually makes sense to either return one or multiple values, depending on call-site context.. I don't think it serves any purpose here ;) > +} > + > sub extract_vzdump_config_tar { > my ($archive, $conf_re) = @_; > > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel@lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > ^ permalink raw reply [flat|nested] 7+ messages in thread
* [pve-devel] [PATCH pve-storage 2/2] clean: fix whitspaces and minor code issues 2023-07-25 14:37 [pve-devel] [PATCH pve-storage/pve-manager 0/3] allow download of compressed ISOs Philipp Hufnagl 2023-07-25 14:37 ` [pve-devel] [PATCH pve-storage 1/2] fix #4849: download-url: allow download and decompression " Philipp Hufnagl @ 2023-07-25 14:37 ` Philipp Hufnagl 2023-07-26 12:31 ` [pve-devel] applied: " Fabian Grünbichler 2023-07-25 14:37 ` [pve-devel] [PATCH pve-manager 1/1] fix #4849: download to storage: automatically detect and configure compression Philipp Hufnagl 2 siblings, 1 reply; 7+ messages in thread From: Philipp Hufnagl @ 2023-07-25 14:37 UTC (permalink / raw) To: pve-devel removed Data::Dumper and a newline Signed-off-by: Philipp Hufnagl <p.hufnagl@proxmox.com> --- src/PVE/API2/Storage/Status.pm | 18 +++++------ src/PVE/Storage.pm | 55 +++++++++++++++++----------------- src/PVE/Storage/Plugin.pm | 3 +- 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.pm index 9ac4660..51e519e 100644 --- a/src/PVE/API2/Storage/Status.pm +++ b/src/PVE/API2/Storage/Status.pm @@ -53,7 +53,7 @@ __PACKAGE__->register_method ({ protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => { node => get_standard_option('pve-node'), storage => get_standard_option('pve-storage-id', { @@ -204,7 +204,7 @@ __PACKAGE__->register_method ({ check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], }, parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => { node => get_standard_option('pve-node'), storage => get_standard_option('pve-storage-id'), @@ -248,7 +248,7 @@ __PACKAGE__->register_method ({ protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => { node => get_standard_option('pve-node'), storage => get_standard_option('pve-storage-id'), @@ -284,7 +284,7 @@ __PACKAGE__->register_method ({ protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => { node => get_standard_option('pve-node'), storage => get_standard_option('pve-storage-id'), @@ -295,11 +295,11 @@ __PACKAGE__->register_method ({ }, ds => { description => "The list of datasources you want to display.", - type => 'string', format => 'pve-configid-list', + type => 'string', format => 'pve-configid-list', }, cf => { description => "The RRD consolidation function", - type => 'string', + type => 'string', enum => [ 'AVERAGE', 'MAX' ], optional => 1, }, @@ -330,7 +330,7 @@ __PACKAGE__->register_method ({ protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => { node => get_standard_option('pve-node'), storage => get_standard_option('pve-storage-id'), @@ -341,7 +341,7 @@ __PACKAGE__->register_method ({ }, cf => { description => "The RRD consolidation function", - type => 'string', + type => 'string', enum => [ 'AVERAGE', 'MAX' ], optional => 1, }, @@ -374,7 +374,7 @@ __PACKAGE__->register_method ({ }, protected => 1, parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => { node => get_standard_option('pve-node'), storage => get_standard_option('pve-storage-id'), diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm index 0c62cc8..4f72790 100755 --- a/src/PVE/Storage.pm +++ b/src/PVE/Storage.pm @@ -2,7 +2,6 @@ package PVE::Storage; use strict; use warnings; -use Data::Dumper; use POSIX; use IO::Select; @@ -304,11 +303,11 @@ sub volume_resize { my ($storeid, $volname) = parse_volume_id($volid, 1); if ($storeid) { - my $scfg = storage_config($cfg, $storeid); - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); - return $plugin->volume_resize($scfg, $storeid, $volname, $size, $running); + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + return $plugin->volume_resize($scfg, $storeid, $volname, $size, $running); } elsif ($volid =~ m|^(/.+)$| && -e $volid) { - die "resize file/device '$volid' is not possible\n"; + die "resize file/device '$volid' is not possible\n"; } else { die "unable to parse volume ID '$volid'\n"; } @@ -319,11 +318,11 @@ sub volume_rollback_is_possible { my ($storeid, $volname) = parse_volume_id($volid, 1); if ($storeid) { - my $scfg = storage_config($cfg, $storeid); - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); - return $plugin->volume_rollback_is_possible($scfg, $storeid, $volname, $snap, $blockers); + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + return $plugin->volume_rollback_is_possible($scfg, $storeid, $volname, $snap, $blockers); } elsif ($volid =~ m|^(/.+)$| && -e $volid) { - die "snapshot rollback file/device '$volid' is not possible\n"; + die "snapshot rollback file/device '$volid' is not possible\n"; } else { die "unable to parse volume ID '$volid'\n"; } @@ -334,11 +333,11 @@ sub volume_snapshot { my ($storeid, $volname) = parse_volume_id($volid, 1); if ($storeid) { - my $scfg = storage_config($cfg, $storeid); - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); - return $plugin->volume_snapshot($scfg, $storeid, $volname, $snap); + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + return $plugin->volume_snapshot($scfg, $storeid, $volname, $snap); } elsif ($volid =~ m|^(/.+)$| && -e $volid) { - die "snapshot file/device '$volid' is not possible\n"; + die "snapshot file/device '$volid' is not possible\n"; } else { die "unable to parse volume ID '$volid'\n"; } @@ -349,12 +348,12 @@ sub volume_snapshot_rollback { my ($storeid, $volname) = parse_volume_id($volid, 1); if ($storeid) { - my $scfg = storage_config($cfg, $storeid); - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); $plugin->volume_rollback_is_possible($scfg, $storeid, $volname, $snap); - return $plugin->volume_snapshot_rollback($scfg, $storeid, $volname, $snap); + return $plugin->volume_snapshot_rollback($scfg, $storeid, $volname, $snap); } elsif ($volid =~ m|^(/.+)$| && -e $volid) { - die "snapshot rollback file/device '$volid' is not possible\n"; + die "snapshot rollback file/device '$volid' is not possible\n"; } else { die "unable to parse volume ID '$volid'\n"; } @@ -366,11 +365,11 @@ sub volume_snapshot_delete { my ($storeid, $volname) = parse_volume_id($volid, 1); if ($storeid) { - my $scfg = storage_config($cfg, $storeid); - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); - return $plugin->volume_snapshot_delete($scfg, $storeid, $volname, $snap, $running); + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + return $plugin->volume_snapshot_delete($scfg, $storeid, $volname, $snap, $running); } elsif ($volid =~ m|^(/.+)$| && -e $volid) { - die "snapshot delete file/device '$volid' is not possible\n"; + die "snapshot delete file/device '$volid' is not possible\n"; } else { die "unable to parse volume ID '$volid'\n"; } @@ -409,9 +408,9 @@ sub volume_has_feature { my ($storeid, $volname) = parse_volume_id($volid, 1); if ($storeid) { - my $scfg = storage_config($cfg, $storeid); - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); - return $plugin->volume_has_feature($scfg, $feature, $storeid, $volname, $snap, $running, $opts); + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + return $plugin->volume_has_feature($scfg, $feature, $storeid, $volname, $snap, $running, $opts); } elsif ($volid =~ m|^(/.+)$| && -e $volid) { return undef; } else { @@ -1503,7 +1502,7 @@ sub foreach_volid { foreach my $sid (keys %$list) { foreach my $info (@{$list->{$sid}}) { - my $volid = $info->{volid}; + my $volid = $info->{volid}; my ($sid1, $volname) = parse_volume_id($volid, 1); if ($sid1 && $sid1 eq $sid) { &$func ($volid, $sid, $info); @@ -1849,7 +1848,7 @@ sub volume_export : prototype($$$$$$$) { my $scfg = storage_config($cfg, $storeid); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); return $plugin->volume_export($scfg, $storeid, $fh, $volname, $format, - $snapshot, $base_snapshot, $with_snapshots); + $snapshot, $base_snapshot, $with_snapshots); } sub volume_import : prototype($$$$$$$$) { @@ -1880,8 +1879,8 @@ sub volume_export_formats : prototype($$$$$) { my $scfg = storage_config($cfg, $storeid); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); return $plugin->volume_export_formats($scfg, $storeid, $volname, - $snapshot, $base_snapshot, - $with_snapshots); + $snapshot, $base_snapshot, + $with_snapshots); } sub volume_import_formats : prototype($$$$$) { diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm index 9d3b1ae..2e75a2b 100644 --- a/src/PVE/Storage/Plugin.pm +++ b/src/PVE/Storage/Plugin.pm @@ -1226,8 +1226,7 @@ my $get_subdir_files = sub { if ($tt eq 'iso') { next if $fn !~ m!/([^/]+$PVE::Storage::ISO_EXT_RE_0)$!i; - - $info = { volid => "$sid:iso/$1", format => 'iso' }; + $info = { volid => "$sid:iso/$1", format => 'iso' }; } elsif ($tt eq 'vztmpl') { next if $fn !~ m!/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$!; -- 2.39.2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [pve-devel] applied: [PATCH pve-storage 2/2] clean: fix whitspaces and minor code issues 2023-07-25 14:37 ` [pve-devel] [PATCH pve-storage 2/2] clean: fix whitspaces and minor code issues Philipp Hufnagl @ 2023-07-26 12:31 ` Fabian Grünbichler 0 siblings, 0 replies; 7+ messages in thread From: Fabian Grünbichler @ 2023-07-26 12:31 UTC (permalink / raw) To: Proxmox VE development discussion applied this one already, with a small fixup folded in. On July 25, 2023 4:37 pm, Philipp Hufnagl wrote: > removed Data::Dumper and a newline > > Signed-off-by: Philipp Hufnagl <p.hufnagl@proxmox.com> > --- > src/PVE/API2/Storage/Status.pm | 18 +++++------ > src/PVE/Storage.pm | 55 +++++++++++++++++----------------- > src/PVE/Storage/Plugin.pm | 3 +- > 3 files changed, 37 insertions(+), 39 deletions(-) > > diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.pm > index 9ac4660..51e519e 100644 > --- a/src/PVE/API2/Storage/Status.pm > +++ b/src/PVE/API2/Storage/Status.pm > @@ -53,7 +53,7 @@ __PACKAGE__->register_method ({ > protected => 1, > proxyto => 'node', > parameters => { > - additionalProperties => 0, > + additionalProperties => 0, > properties => { > node => get_standard_option('pve-node'), > storage => get_standard_option('pve-storage-id', { > @@ -204,7 +204,7 @@ __PACKAGE__->register_method ({ > check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], > }, > parameters => { > - additionalProperties => 0, > + additionalProperties => 0, > properties => { > node => get_standard_option('pve-node'), > storage => get_standard_option('pve-storage-id'), > @@ -248,7 +248,7 @@ __PACKAGE__->register_method ({ > protected => 1, > proxyto => 'node', > parameters => { > - additionalProperties => 0, > + additionalProperties => 0, > properties => { > node => get_standard_option('pve-node'), > storage => get_standard_option('pve-storage-id'), > @@ -284,7 +284,7 @@ __PACKAGE__->register_method ({ > protected => 1, > proxyto => 'node', > parameters => { > - additionalProperties => 0, > + additionalProperties => 0, > properties => { > node => get_standard_option('pve-node'), > storage => get_standard_option('pve-storage-id'), > @@ -295,11 +295,11 @@ __PACKAGE__->register_method ({ > }, > ds => { > description => "The list of datasources you want to display.", > - type => 'string', format => 'pve-configid-list', > + type => 'string', format => 'pve-configid-list', > }, > cf => { > description => "The RRD consolidation function", > - type => 'string', > + type => 'string', > enum => [ 'AVERAGE', 'MAX' ], > optional => 1, > }, > @@ -330,7 +330,7 @@ __PACKAGE__->register_method ({ > protected => 1, > proxyto => 'node', > parameters => { > - additionalProperties => 0, > + additionalProperties => 0, > properties => { > node => get_standard_option('pve-node'), > storage => get_standard_option('pve-storage-id'), > @@ -341,7 +341,7 @@ __PACKAGE__->register_method ({ > }, > cf => { > description => "The RRD consolidation function", > - type => 'string', > + type => 'string', > enum => [ 'AVERAGE', 'MAX' ], > optional => 1, > }, > @@ -374,7 +374,7 @@ __PACKAGE__->register_method ({ > }, > protected => 1, > parameters => { > - additionalProperties => 0, > + additionalProperties => 0, > properties => { > node => get_standard_option('pve-node'), > storage => get_standard_option('pve-storage-id'), > diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm > index 0c62cc8..4f72790 100755 > --- a/src/PVE/Storage.pm > +++ b/src/PVE/Storage.pm > @@ -2,7 +2,6 @@ package PVE::Storage; > > use strict; > use warnings; > -use Data::Dumper; > > use POSIX; > use IO::Select; > @@ -304,11 +303,11 @@ sub volume_resize { > > my ($storeid, $volname) = parse_volume_id($volid, 1); > if ($storeid) { > - my $scfg = storage_config($cfg, $storeid); > - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > - return $plugin->volume_resize($scfg, $storeid, $volname, $size, $running); > + my $scfg = storage_config($cfg, $storeid); > + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > + return $plugin->volume_resize($scfg, $storeid, $volname, $size, $running); > } elsif ($volid =~ m|^(/.+)$| && -e $volid) { > - die "resize file/device '$volid' is not possible\n"; > + die "resize file/device '$volid' is not possible\n"; > } else { > die "unable to parse volume ID '$volid'\n"; > } > @@ -319,11 +318,11 @@ sub volume_rollback_is_possible { > > my ($storeid, $volname) = parse_volume_id($volid, 1); > if ($storeid) { > - my $scfg = storage_config($cfg, $storeid); > - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > - return $plugin->volume_rollback_is_possible($scfg, $storeid, $volname, $snap, $blockers); > + my $scfg = storage_config($cfg, $storeid); > + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > + return $plugin->volume_rollback_is_possible($scfg, $storeid, $volname, $snap, $blockers); > } elsif ($volid =~ m|^(/.+)$| && -e $volid) { > - die "snapshot rollback file/device '$volid' is not possible\n"; > + die "snapshot rollback file/device '$volid' is not possible\n"; > } else { > die "unable to parse volume ID '$volid'\n"; > } > @@ -334,11 +333,11 @@ sub volume_snapshot { > > my ($storeid, $volname) = parse_volume_id($volid, 1); > if ($storeid) { > - my $scfg = storage_config($cfg, $storeid); > - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > - return $plugin->volume_snapshot($scfg, $storeid, $volname, $snap); > + my $scfg = storage_config($cfg, $storeid); > + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > + return $plugin->volume_snapshot($scfg, $storeid, $volname, $snap); > } elsif ($volid =~ m|^(/.+)$| && -e $volid) { > - die "snapshot file/device '$volid' is not possible\n"; > + die "snapshot file/device '$volid' is not possible\n"; > } else { > die "unable to parse volume ID '$volid'\n"; > } > @@ -349,12 +348,12 @@ sub volume_snapshot_rollback { > > my ($storeid, $volname) = parse_volume_id($volid, 1); > if ($storeid) { > - my $scfg = storage_config($cfg, $storeid); > - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > + my $scfg = storage_config($cfg, $storeid); > + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > $plugin->volume_rollback_is_possible($scfg, $storeid, $volname, $snap); > - return $plugin->volume_snapshot_rollback($scfg, $storeid, $volname, $snap); > + return $plugin->volume_snapshot_rollback($scfg, $storeid, $volname, $snap); > } elsif ($volid =~ m|^(/.+)$| && -e $volid) { > - die "snapshot rollback file/device '$volid' is not possible\n"; > + die "snapshot rollback file/device '$volid' is not possible\n"; > } else { > die "unable to parse volume ID '$volid'\n"; > } > @@ -366,11 +365,11 @@ sub volume_snapshot_delete { > > my ($storeid, $volname) = parse_volume_id($volid, 1); > if ($storeid) { > - my $scfg = storage_config($cfg, $storeid); > - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > - return $plugin->volume_snapshot_delete($scfg, $storeid, $volname, $snap, $running); > + my $scfg = storage_config($cfg, $storeid); > + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > + return $plugin->volume_snapshot_delete($scfg, $storeid, $volname, $snap, $running); > } elsif ($volid =~ m|^(/.+)$| && -e $volid) { > - die "snapshot delete file/device '$volid' is not possible\n"; > + die "snapshot delete file/device '$volid' is not possible\n"; > } else { > die "unable to parse volume ID '$volid'\n"; > } > @@ -409,9 +408,9 @@ sub volume_has_feature { > > my ($storeid, $volname) = parse_volume_id($volid, 1); > if ($storeid) { > - my $scfg = storage_config($cfg, $storeid); > - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > - return $plugin->volume_has_feature($scfg, $feature, $storeid, $volname, $snap, $running, $opts); > + my $scfg = storage_config($cfg, $storeid); > + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > + return $plugin->volume_has_feature($scfg, $feature, $storeid, $volname, $snap, $running, $opts); > } elsif ($volid =~ m|^(/.+)$| && -e $volid) { > return undef; > } else { > @@ -1503,7 +1502,7 @@ sub foreach_volid { > > foreach my $sid (keys %$list) { > foreach my $info (@{$list->{$sid}}) { > - my $volid = $info->{volid}; > + my $volid = $info->{volid}; > my ($sid1, $volname) = parse_volume_id($volid, 1); > if ($sid1 && $sid1 eq $sid) { > &$func ($volid, $sid, $info); > @@ -1849,7 +1848,7 @@ sub volume_export : prototype($$$$$$$) { > my $scfg = storage_config($cfg, $storeid); > my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > return $plugin->volume_export($scfg, $storeid, $fh, $volname, $format, > - $snapshot, $base_snapshot, $with_snapshots); > + $snapshot, $base_snapshot, $with_snapshots); > } > > sub volume_import : prototype($$$$$$$$) { > @@ -1880,8 +1879,8 @@ sub volume_export_formats : prototype($$$$$) { > my $scfg = storage_config($cfg, $storeid); > my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > return $plugin->volume_export_formats($scfg, $storeid, $volname, > - $snapshot, $base_snapshot, > - $with_snapshots); > + $snapshot, $base_snapshot, > + $with_snapshots); > } > > sub volume_import_formats : prototype($$$$$) { > diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm > index 9d3b1ae..2e75a2b 100644 > --- a/src/PVE/Storage/Plugin.pm > +++ b/src/PVE/Storage/Plugin.pm > @@ -1226,8 +1226,7 @@ my $get_subdir_files = sub { > > if ($tt eq 'iso') { > next if $fn !~ m!/([^/]+$PVE::Storage::ISO_EXT_RE_0)$!i; > - > - $info = { volid => "$sid:iso/$1", format => 'iso' }; > + $info = { volid => "$sid:iso/$1", format => 'iso' }; > > } elsif ($tt eq 'vztmpl') { > next if $fn !~ m!/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$!; > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel@lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > ^ permalink raw reply [flat|nested] 7+ messages in thread
* [pve-devel] [PATCH pve-manager 1/1] fix #4849: download to storage: automatically detect and configure compression 2023-07-25 14:37 [pve-devel] [PATCH pve-storage/pve-manager 0/3] allow download of compressed ISOs Philipp Hufnagl 2023-07-25 14:37 ` [pve-devel] [PATCH pve-storage 1/2] fix #4849: download-url: allow download and decompression " Philipp Hufnagl 2023-07-25 14:37 ` [pve-devel] [PATCH pve-storage 2/2] clean: fix whitspaces and minor code issues Philipp Hufnagl @ 2023-07-25 14:37 ` Philipp Hufnagl 2023-07-26 12:31 ` Fabian Grünbichler 2 siblings, 1 reply; 7+ messages in thread From: Philipp Hufnagl @ 2023-07-25 14:37 UTC (permalink / raw) To: pve-devel Signed-off-by: Philipp Hufnagl <p.hufnagl@proxmox.com> --- PVE/API2/Nodes.pm | 21 ++++++++++++++++- www/manager6/Makefile | 1 + www/manager6/form/DecompressionSelector.js | 14 +++++++++++ www/manager6/window/DownloadUrlToStorage.js | 26 +++++++++++++++++++-- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 www/manager6/form/DecompressionSelector.js diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm index 9269694d..de16d9a6 100644 --- a/PVE/API2/Nodes.pm +++ b/PVE/API2/Nodes.pm @@ -1564,6 +1564,12 @@ __PACKAGE__->register_method({ type => 'boolean', optional => 1, default => 1, + }, + 'find-compression' => { + description => "If true an auto detuction of used compression will be attempted", + type => 'boolean', + optional => 1, + default => 0, } }, }, @@ -1583,6 +1589,10 @@ __PACKAGE__->register_method({ type => 'string', optional => 1, }, + compression => { + type => 'string', + optional => 1, + }, }, }, code => sub { @@ -1606,6 +1616,8 @@ __PACKAGE__->register_method({ ); } + my $find_compression = $param->{'find-compression'}; + my $req = HTTP::Request->new(HEAD => $url); my $res = $ua->request($req); @@ -1614,7 +1626,7 @@ __PACKAGE__->register_method({ my $size = $res->header("Content-Length"); my $disposition = $res->header("Content-Disposition"); my $type = $res->header("Content-Type"); - + my $compression = "__default__"; my $filename; if ($disposition && ($disposition =~ m/filename="([^"]*)"/ || $disposition =~ m/filename=([^;]*)/)) { @@ -1628,10 +1640,17 @@ __PACKAGE__->register_method({ $type = $1; } + if($find_compression && $filename && $filename =~ m!((^.+)(?:\.(${\PVE::Storage::Plugin::COMPRESSOR_RE})))$! && $3) + { + $filename = $2 ; + $compression = "$3"; + } + my $ret = {}; $ret->{filename} = $filename if $filename; $ret->{size} = $size + 0 if $size; $ret->{mimetype} = $type if $type; + $ret->{compression} = $compression if $compression; return $ret; }}); diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 7ec9d7a5..42a27548 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -34,6 +34,7 @@ JSSRC= \ form/ContentTypeSelector.js \ form/ControllerSelector.js \ form/DayOfWeekSelector.js \ + form/DecompressionSelector.js \ form/DiskFormatSelector.js \ form/DiskStorageSelector.js \ form/EmailNotificationSelector.js \ diff --git a/www/manager6/form/DecompressionSelector.js b/www/manager6/form/DecompressionSelector.js new file mode 100644 index 00000000..35d8d738 --- /dev/null +++ b/www/manager6/form/DecompressionSelector.js @@ -0,0 +1,14 @@ + +Ext.define('PVE.form.DecompressionSelector', { + extend: 'Proxmox.form.KVComboBox', + alias: ['widget.pveDecompressionSelector'], + config: { + deleteEmpty: false, + }, + comboItems: [ + ['__default__', 'None'], + ['lzo', 'LZO'], + ['gz', 'GZIP'], + ['zst', 'ZSTD'], + ], +}); \ No newline at end of file diff --git a/www/manager6/window/DownloadUrlToStorage.js b/www/manager6/window/DownloadUrlToStorage.js index 48543d28..b40b7ea6 100644 --- a/www/manager6/window/DownloadUrlToStorage.js +++ b/www/manager6/window/DownloadUrlToStorage.js @@ -49,6 +49,9 @@ Ext.define('PVE.window.DownloadUrlToStorage', { vm.set('size', '-'); vm.set('mimetype', '-'); }, + decompressonPossible: function() { + return this.view.content === 'iso'; + }, urlCheck: function(field) { let me = this; @@ -66,6 +69,7 @@ Ext.define('PVE.window.DownloadUrlToStorage', { params: { url: queryParam.url, 'verify-certificates': queryParam['verify-certificates'], + 'find-compression': me.decompressonPossible() ? 1 : 0 , }, waitMsgTarget: view, failure: res => { @@ -84,6 +88,7 @@ Ext.define('PVE.window.DownloadUrlToStorage', { filename: data.filename || "", size: (data.size && Proxmox.Utils.format_size(data.size)) || gettext("Unknown"), mimetype: data.mimetype || gettext("Unknown"), + compression: data.compression || '__default__', }); }, }); @@ -215,7 +220,7 @@ Ext.define('PVE.window.DownloadUrlToStorage', { ], initComponent: function() { - var me = this; + var me = this; if (!me.nodename) { throw "no node name specified"; @@ -223,8 +228,25 @@ Ext.define('PVE.window.DownloadUrlToStorage', { if (!me.storage) { throw "no storage ID specified"; } + if(me.content === 'iso') + { + me.items[0].advancedColumn2.push( + + { + xtype: 'pveDecompressionSelector', + name: 'compression', + fieldLabel: gettext('Decompression algorithm'), + allowBlank: true, + hasNoneOption: true, + value: '__default__', + listeners: { + change: 'decryptorChange', + }, + }); + + } - me.callParent(); + me.callParent(); }, }); -- 2.39.2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [pve-devel] [PATCH pve-manager 1/1] fix #4849: download to storage: automatically detect and configure compression 2023-07-25 14:37 ` [pve-devel] [PATCH pve-manager 1/1] fix #4849: download to storage: automatically detect and configure compression Philipp Hufnagl @ 2023-07-26 12:31 ` Fabian Grünbichler 0 siblings, 0 replies; 7+ messages in thread From: Fabian Grünbichler @ 2023-07-26 12:31 UTC (permalink / raw) To: Proxmox VE development discussion On July 25, 2023 4:37 pm, Philipp Hufnagl wrote: > Signed-off-by: Philipp Hufnagl <p.hufnagl@proxmox.com> > --- > PVE/API2/Nodes.pm | 21 ++++++++++++++++- > www/manager6/Makefile | 1 + > www/manager6/form/DecompressionSelector.js | 14 +++++++++++ > www/manager6/window/DownloadUrlToStorage.js | 26 +++++++++++++++++++-- > 4 files changed, 59 insertions(+), 3 deletions(-) > create mode 100644 www/manager6/form/DecompressionSelector.js > > diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm > index 9269694d..de16d9a6 100644 > --- a/PVE/API2/Nodes.pm > +++ b/PVE/API2/Nodes.pm > @@ -1564,6 +1564,12 @@ __PACKAGE__->register_method({ > type => 'boolean', > optional => 1, > default => 1, > + }, > + 'find-compression' => { > + description => "If true an auto detuction of used compression will be attempted", typo ;) detect might be better naming than find > + type => 'boolean', > + optional => 1, > + default => 0, > } > }, > }, > @@ -1583,6 +1589,10 @@ __PACKAGE__->register_method({ > type => 'string', > optional => 1, > }, > + compression => { > + type => 'string', could get the same enum annotation that the parameter in pve-storage should get - but if it is included here, it should probably be defined once in pve-storage, so that updates there don't need to be duplicated here as well.. > + optional => 1, > + }, > }, > }, > code => sub { > @@ -1606,6 +1616,8 @@ __PACKAGE__->register_method({ > ); > } > > + my $find_compression = $param->{'find-compression'}; > + > my $req = HTTP::Request->new(HEAD => $url); > my $res = $ua->request($req); > > @@ -1614,7 +1626,7 @@ __PACKAGE__->register_method({ > my $size = $res->header("Content-Length"); > my $disposition = $res->header("Content-Disposition"); > my $type = $res->header("Content-Type"); > - > + my $compression = "__default__"; this is wrong, there cannot be a default compression that is detected (and if there were, it shouldn't be the placeholder special value used in the GUI!) > my $filename; > > if ($disposition && ($disposition =~ m/filename="([^"]*)"/ || $disposition =~ m/filename=([^;]*)/)) { > @@ -1628,10 +1640,17 @@ __PACKAGE__->register_method({ > $type = $1; > } > > + if($find_compression && $filename && $filename =~ m!((^.+)(?:\.(${\PVE::Storage::Plugin::COMPRESSOR_RE})))$! && $3) style! missing space after if, and way too long condition.. the non-capturing group and the outermost capturing one can be dropped AFAICT, just m!(^.+)\.(${\PVE::Storage::Plugin::COMPRESSOR_RE})$! would be fine grouping wise. but the RE is still wrong, it should be properly anchored (I am not sure whether the first group was an attempt to anchor, or an attempt to exclude the '.' character?). probably what you meant was something like m!^(.+)\.(${\PVE::Storage::Plugin::COMPRESSOR_RE})$! but, please double check :) the last part of the condition can also be dropped, since if the RE matched, $3 must be defined. the RE could be moved into a variable > + { style! > + $filename = $2 ; > + $compression = "$3"; indentation, and whitespace sneaking in ;) also, why is one captured group quoted and the other not? > + } > + > my $ret = {}; > $ret->{filename} = $filename if $filename; > $ret->{size} = $size + 0 if $size; > $ret->{mimetype} = $type if $type; > + $ret->{compression} = $compression if $compression; > > return $ret; > }}); > diff --git a/www/manager6/Makefile b/www/manager6/Makefile > index 7ec9d7a5..42a27548 100644 > --- a/www/manager6/Makefile > +++ b/www/manager6/Makefile > @@ -34,6 +34,7 @@ JSSRC= \ > form/ContentTypeSelector.js \ > form/ControllerSelector.js \ > form/DayOfWeekSelector.js \ > + form/DecompressionSelector.js \ > form/DiskFormatSelector.js \ > form/DiskStorageSelector.js \ > form/EmailNotificationSelector.js \ > diff --git a/www/manager6/form/DecompressionSelector.js b/www/manager6/form/DecompressionSelector.js > new file mode 100644 > index 00000000..35d8d738 > --- /dev/null > +++ b/www/manager6/form/DecompressionSelector.js > @@ -0,0 +1,14 @@ > + > +Ext.define('PVE.form.DecompressionSelector', { > + extend: 'Proxmox.form.KVComboBox', > + alias: ['widget.pveDecompressionSelector'], > + config: { > + deleteEmpty: false, > + }, > + comboItems: [ > + ['__default__', 'None'], there is a Proxmox.Utils.noneText (which just wraps `gettext('None')`). > + ['lzo', 'LZO'], > + ['gz', 'GZIP'], > + ['zst', 'ZSTD'], > + ], > +}); > \ No newline at end of file ? eslint is also unhappy: [./form/DecompressionSelector.js]: WARN: line 14 col 4: eol-last - Newline required at end of file but not found. (*) > diff --git a/www/manager6/window/DownloadUrlToStorage.js b/www/manager6/window/DownloadUrlToStorage.js > index 48543d28..b40b7ea6 100644 > --- a/www/manager6/window/DownloadUrlToStorage.js > +++ b/www/manager6/window/DownloadUrlToStorage.js [./window/DownloadUrlToStorage.js]: WARN: line 72 col 61: comma-spacing - There should be no space before ','. (*) WARN: line 231 col 2: keyword-spacing - Expected space(s) after "if". (*) WARN: line 232 col 2: brace-style - Opening curly brace does not appear on the same line as controlling statement. (*) WARN: line 245 col 6: padded-blocks - Block must not be padded by blank lines. (*) please make these eslint warnings happy.. > @@ -49,6 +49,9 @@ Ext.define('PVE.window.DownloadUrlToStorage', { > vm.set('size', '-'); > vm.set('mimetype', '-'); > }, > + decompressonPossible: function() { typo in function name > + return this.view.content === 'iso'; > + }, > > urlCheck: function(field) { > let me = this; > @@ -66,6 +69,7 @@ Ext.define('PVE.window.DownloadUrlToStorage', { > params: { > url: queryParam.url, > 'verify-certificates': queryParam['verify-certificates'], > + 'find-compression': me.decompressonPossible() ? 1 : 0 , > }, > waitMsgTarget: view, > failure: res => { > @@ -84,6 +88,7 @@ Ext.define('PVE.window.DownloadUrlToStorage', { > filename: data.filename || "", > size: (data.size && Proxmox.Utils.format_size(data.size)) || gettext("Unknown"), > mimetype: data.mimetype || gettext("Unknown"), > + compression: data.compression || '__default__', > }); > }, > }); > @@ -215,7 +220,7 @@ Ext.define('PVE.window.DownloadUrlToStorage', { > ], > > initComponent: function() { > - var me = this; > + var me = this; > > if (!me.nodename) { > throw "no node name specified"; > @@ -223,8 +228,25 @@ Ext.define('PVE.window.DownloadUrlToStorage', { > if (!me.storage) { > throw "no storage ID specified"; > } > + if(me.content === 'iso') > + { > + me.items[0].advancedColumn2.push( > + > + { > + xtype: 'pveDecompressionSelector', > + name: 'compression', > + fieldLabel: gettext('Decompression algorithm'), > + allowBlank: true, > + hasNoneOption: true, > + value: '__default__', > + listeners: { > + change: 'decryptorChange', decryptorChange is not defined anywhere > + }, > + }); > + > + } > > - me.callParent(); > + me.callParent(); > }, > }); > > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel@lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2023-07-26 12:31 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2023-07-25 14:37 [pve-devel] [PATCH pve-storage/pve-manager 0/3] allow download of compressed ISOs Philipp Hufnagl 2023-07-25 14:37 ` [pve-devel] [PATCH pve-storage 1/2] fix #4849: download-url: allow download and decompression " Philipp Hufnagl 2023-07-26 12:31 ` Fabian Grünbichler 2023-07-25 14:37 ` [pve-devel] [PATCH pve-storage 2/2] clean: fix whitspaces and minor code issues Philipp Hufnagl 2023-07-26 12:31 ` [pve-devel] applied: " Fabian Grünbichler 2023-07-25 14:37 ` [pve-devel] [PATCH pve-manager 1/1] fix #4849: download to storage: automatically detect and configure compression Philipp Hufnagl 2023-07-26 12:31 ` Fabian Grünbichler
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox