]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/APT.pm
e9d27f4b3bcf1aa8ad7a6996f23b67b00a33e4ac
[pve-manager.git] / PVE / API2 / APT.pm
1 package PVE::API2::APT;
2
3 use strict;
4 use warnings;
5
6 use POSIX;
7 use File::stat ();
8 use IO::File;
9 use File::Basename;
10
11 use LWP::UserAgent;
12
13 use Proxmox::RS::APT::Repositories;
14
15 use PVE::pvecfg;
16 use PVE::Tools qw(extract_param);
17 use PVE::Cluster;
18 use PVE::DataCenterConfig;
19 use PVE::SafeSyslog;
20 use PVE::INotify;
21 use PVE::Exception;
22 use PVE::RESTHandler;
23 use PVE::RPCEnvironment;
24 use PVE::API2Tools;
25
26 use JSON;
27 use PVE::JSONSchema qw(get_standard_option);
28
29 use AptPkg::Cache;
30 use AptPkg::PkgRecords;
31 use AptPkg::System;
32
33 my $get_apt_cache = sub {
34
35 my $apt_cache = AptPkg::Cache->new() || die "unable to initialize AptPkg::Cache\n";
36
37 return $apt_cache;
38 };
39
40 use base qw(PVE::RESTHandler);
41
42 __PACKAGE__->register_method({
43 name => 'index',
44 path => '',
45 method => 'GET',
46 description => "Directory index for apt (Advanced Package Tool).",
47 permissions => {
48 user => 'all',
49 },
50 parameters => {
51 additionalProperties => 0,
52 properties => {
53 node => get_standard_option('pve-node'),
54 },
55 },
56 returns => {
57 type => "array",
58 items => {
59 type => "object",
60 properties => {
61 id => { type => 'string' },
62 },
63 },
64 links => [ { rel => 'child', href => "{id}" } ],
65 },
66 code => sub {
67 my ($param) = @_;
68
69 my $res = [
70 { id => 'changelog' },
71 { id => 'repositories' },
72 { id => 'update' },
73 { id => 'versions' },
74 ];
75
76 return $res;
77 }});
78
79 my $get_pkgfile = sub {
80 my ($veriter) = @_;
81
82 foreach my $verfile (@{$veriter->{FileList}}) {
83 my $pkgfile = $verfile->{File};
84 next if !$pkgfile->{Origin};
85 return $pkgfile;
86 }
87
88 return undef;
89 };
90
91 my $get_changelog_url =sub {
92 my ($pkgname, $info, $pkgver, $origin, $component) = @_;
93
94 my $changelog_url;
95 my $base;
96 $base = dirname($info->{FileName}) if defined($info->{FileName});
97 if ($origin && $base) {
98 $pkgver =~ s/^\d+://; # strip epoch
99 my $srcpkg = $info->{SourcePkg} || $pkgname;
100 if ($origin eq 'Debian') {
101 $base =~ s!pool/updates/!pool/!; # for security channel
102 $changelog_url = "http://packages.debian.org/changelogs/$base/${srcpkg}_${pkgver}/changelog";
103 } elsif ($origin eq 'Proxmox') {
104 if ($component eq 'pve-enterprise') {
105 $changelog_url = "https://enterprise.proxmox.com/debian/$base/${pkgname}_${pkgver}.changelog";
106 } else {
107 $changelog_url = "http://download.proxmox.com/debian/$base/${pkgname}_${pkgver}.changelog";
108 }
109 }
110 }
111
112 return $changelog_url;
113 };
114
115 my $assemble_pkginfo = sub {
116 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
117
118 my $data = {
119 Package => $info->{Name},
120 Title => $info->{ShortDesc},
121 Origin => 'unknown',
122 };
123
124 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
125 $data->{Origin} = $pkgfile->{Origin};
126 if (my $changelog_url = &$get_changelog_url($pkgname, $info, $candidate_ver->{VerStr},
127 $pkgfile->{Origin}, $pkgfile->{Component})) {
128 $data->{ChangeLogUrl} = $changelog_url;
129 }
130 }
131
132 if (my $desc = $info->{LongDesc}) {
133 $desc =~ s/^.*\n\s?//; # remove first line
134 $desc =~ s/\n / /g;
135 $data->{Description} = $desc;
136 }
137
138 foreach my $k (qw(Section Arch Priority)) {
139 $data->{$k} = $candidate_ver->{$k};
140 }
141
142 $data->{Version} = $candidate_ver->{VerStr};
143 $data->{OldVersion} = $current_ver->{VerStr} if $current_ver;
144
145 return $data;
146 };
147
148 # we try to cache results
149 my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates";
150
151 my $read_cached_pkgstatus = sub {
152 my $data = [];
153 eval {
154 my $jsonstr = PVE::Tools::file_get_contents($pve_pkgstatus_fn, 5*1024*1024);
155 $data = decode_json($jsonstr);
156 };
157 if (my $err = $@) {
158 warn "error reading cached package status in $pve_pkgstatus_fn\n";
159 }
160 return $data;
161 };
162
163 my $update_pve_pkgstatus = sub {
164
165 syslog('info', "update new package list: $pve_pkgstatus_fn");
166
167 my $notify_status = {};
168 my $oldpkglist = &$read_cached_pkgstatus();
169 foreach my $pi (@$oldpkglist) {
170 $notify_status->{$pi->{Package}} = $pi->{NotifyStatus};
171 }
172
173 my $pkglist = [];
174
175 my $cache = &$get_apt_cache();
176 my $policy = $cache->policy;
177 my $pkgrecords = $cache->packages();
178
179 foreach my $pkgname (keys %$cache) {
180 my $p = $cache->{$pkgname};
181 next if !$p->{SelectedState} || ($p->{SelectedState} ne 'Install');
182 my $current_ver = $p->{CurrentVer} || next;
183 my $candidate_ver = $policy->candidate($p) || next;
184
185 if ($current_ver->{VerStr} ne $candidate_ver->{VerStr}) {
186 my $info = $pkgrecords->lookup($pkgname);
187 my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver);
188 push @$pkglist, $res;
189
190 # also check if we need any new package
191 # Note: this is just a quick hack (not recursive as it should be), because
192 # I found no way to get that info from AptPkg
193 if (my $deps = $candidate_ver->{DependsList}) {
194 my $found;
195 my $req;
196 for my $d (@$deps) {
197 if ($d->{DepType} eq 'Depends') {
198 $found = $d->{TargetPkg}->{SelectedState} eq 'Install' if !$found;
199 # need to check ProvidesList for virtual packages
200 if (!$found && (my $provides = $d->{TargetPkg}->{ProvidesList})) {
201 for my $provide ($provides->@*) {
202 $found = $provide->{OwnerPkg}->{SelectedState} eq 'Install';
203 last if $found;
204 }
205 }
206 $req = $d->{TargetPkg} if !$req;
207
208 if (!($d->{CompType} & AptPkg::Dep::Or)) {
209 if (!$found && $req) { # New required Package
210 my $tpname = $req->{Name};
211 my $tpinfo = $pkgrecords->lookup($tpname);
212 my $tpcv = $policy->candidate($req);
213 if ($tpinfo && $tpcv) {
214 my $res = &$assemble_pkginfo($tpname, $tpinfo, undef, $tpcv);
215 push @$pkglist, $res;
216 }
217 }
218 undef $found;
219 undef $req;
220 }
221 }
222 }
223 }
224 }
225 }
226
227 # keep notification status (avoid sending mails abou new packages more than once)
228 foreach my $pi (@$pkglist) {
229 if (my $ns = $notify_status->{$pi->{Package}}) {
230 $pi->{NotifyStatus} = $ns if $ns eq $pi->{Version};
231 }
232 }
233
234 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
235
236 return $pkglist;
237 };
238
239 __PACKAGE__->register_method({
240 name => 'list_updates',
241 path => 'update',
242 method => 'GET',
243 description => "List available updates.",
244 permissions => {
245 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
246 },
247 protected => 1,
248 proxyto => 'node',
249 parameters => {
250 additionalProperties => 0,
251 properties => {
252 node => get_standard_option('pve-node'),
253 },
254 },
255 returns => {
256 type => "array",
257 items => {
258 type => "object",
259 properties => {},
260 },
261 },
262 code => sub {
263 my ($param) = @_;
264
265 if (my $st1 = File::stat::stat($pve_pkgstatus_fn)) {
266 my $st2 = File::stat::stat("/var/cache/apt/pkgcache.bin");
267 my $st3 = File::stat::stat("/var/lib/dpkg/status");
268
269 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
270 if (my $data = &$read_cached_pkgstatus()) {
271 return $data;
272 }
273 }
274 }
275
276 my $pkglist = &$update_pve_pkgstatus();
277
278 return $pkglist;
279 }});
280
281 __PACKAGE__->register_method({
282 name => 'update_database',
283 path => 'update',
284 method => 'POST',
285 description => "This is used to resynchronize the package index files from their sources (apt-get update).",
286 permissions => {
287 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
288 },
289 protected => 1,
290 proxyto => 'node',
291 parameters => {
292 additionalProperties => 0,
293 properties => {
294 node => get_standard_option('pve-node'),
295 notify => {
296 type => 'boolean',
297 description => "Send notification mail about new packages (to email address specified for user 'root\@pam').",
298 optional => 1,
299 default => 0,
300 },
301 quiet => {
302 type => 'boolean',
303 description => "Only produces output suitable for logging, omitting progress indicators.",
304 optional => 1,
305 default => 0,
306 },
307 },
308 },
309 returns => {
310 type => 'string',
311 },
312 code => sub {
313 my ($param) = @_;
314
315 my $rpcenv = PVE::RPCEnvironment::get();
316
317 my $authuser = $rpcenv->get_user();
318
319 my $realcmd = sub {
320 my $upid = shift;
321
322 # setup proxy for apt
323 my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
324
325 my $aptconf = "// no proxy configured\n";
326 if ($dcconf->{http_proxy}) {
327 $aptconf = "Acquire::http::Proxy \"$dcconf->{http_proxy}\";\n";
328 }
329 my $aptcfn = "/etc/apt/apt.conf.d/76pveproxy";
330 PVE::Tools::file_set_contents($aptcfn, $aptconf);
331
332 my $cmd = ['apt-get', 'update'];
333
334 print "starting apt-get update\n" if !$param->{quiet};
335
336 if ($param->{quiet}) {
337 PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {});
338 } else {
339 PVE::Tools::run_command($cmd);
340 }
341
342 my $pkglist = &$update_pve_pkgstatus();
343
344 if ($param->{notify} && scalar(@$pkglist)) {
345
346 my $usercfg = PVE::Cluster::cfs_read_file("user.cfg");
347 my $rootcfg = $usercfg->{users}->{'root@pam'} || {};
348 my $mailto = $rootcfg->{email};
349
350 if ($mailto) {
351 my $hostname = `hostname -f` || PVE::INotify::nodename();
352 chomp $hostname;
353 my $mailfrom = $dcconf->{email_from} || "root";
354 my $subject = "New software packages available ($hostname)";
355
356 my $data = "The following updates are available:\n\n";
357
358 my $count = 0;
359 foreach my $p (sort {$a->{Package} cmp $b->{Package} } @$pkglist) {
360 next if $p->{NotifyStatus} && $p->{NotifyStatus} eq $p->{Version};
361 $count++;
362 if ($p->{OldVersion}) {
363 $data .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
364 } else {
365 $data .= "$p->{Package}: $p->{Version} (new)\n";
366 }
367 }
368
369 return if !$count;
370
371 PVE::Tools::sendmail($mailto, $subject, $data, undef, $mailfrom, '');
372
373 foreach my $pi (@$pkglist) {
374 $pi->{NotifyStatus} = $pi->{Version};
375 }
376 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
377 }
378 }
379
380 return;
381 };
382
383 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
384
385 }});
386
387 __PACKAGE__->register_method({
388 name => 'changelog',
389 path => 'changelog',
390 method => 'GET',
391 description => "Get package changelogs.",
392 permissions => {
393 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
394 },
395 proxyto => 'node',
396 parameters => {
397 additionalProperties => 0,
398 properties => {
399 node => get_standard_option('pve-node'),
400 name => {
401 description => "Package name.",
402 type => 'string',
403 },
404 version => {
405 description => "Package version.",
406 type => 'string',
407 optional => 1,
408 },
409 },
410 },
411 returns => {
412 type => "string",
413 },
414 code => sub {
415 my ($param) = @_;
416
417 my $pkgname = $param->{name};
418
419 my $cache = &$get_apt_cache();
420 my $policy = $cache->policy;
421 my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n";
422 my $pkgrecords = $cache->packages();
423
424 my $ver;
425 if ($param->{version}) {
426 if (my $available = $p->{VersionList}) {
427 for my $v (@$available) {
428 if ($v->{VerStr} eq $param->{version}) {
429 $ver = $v;
430 last;
431 }
432 }
433 }
434 die "package '$pkgname' version '$param->{version}' is not available\n" if !$ver;
435 } else {
436 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
437 }
438
439 my $info = $pkgrecords->lookup($pkgname);
440
441 my $pkgfile = &$get_pkgfile($ver);
442 my $url;
443
444 die "changelog for '${pkgname}_$ver->{VerStr}' not available\n"
445 if !($pkgfile && ($url = &$get_changelog_url($pkgname, $info, $ver->{VerStr}, $pkgfile->{Origin}, $pkgfile->{Component})));
446
447 my $data = "";
448
449 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
450 my $proxy = $dccfg->{http_proxy};
451
452 my $ua = LWP::UserAgent->new;
453 $ua->agent("PVE/1.0");
454 $ua->timeout(10);
455 $ua->max_size(1024*1024);
456 $ua->ssl_opts(verify_hostname => 0); # don't care for changelogs
457
458 if ($proxy) {
459 $ua->proxy(['http', 'https'], $proxy);
460 } else {
461 $ua->env_proxy;
462 }
463
464 my $username;
465 my $pw;
466
467 if ($pkgfile->{Origin} eq 'Proxmox' && $pkgfile->{Component} eq 'pve-enterprise') {
468 my $info = PVE::API2::Subscription::read_etc_subscription();
469 if ($info->{status} eq 'active') {
470 $username = $info->{key};
471 $pw = PVE::API2Tools::get_hwaddress();
472 $ua->credentials("enterprise.proxmox.com:443", 'pve-enterprise-repository', $username, $pw);
473 }
474 }
475
476 my $response = $ua->get($url);
477
478 if ($response->is_success) {
479 $data = $response->decoded_content;
480 } else {
481 PVE::Exception::raise($response->message, code => $response->code);
482 }
483
484 return $data;
485 }});
486
487 __PACKAGE__->register_method({
488 name => 'repositories',
489 path => 'repositories',
490 method => 'GET',
491 proxyto => 'node',
492 description => "Get APT repository information.",
493 permissions => {
494 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
495 },
496 parameters => {
497 additionalProperties => 0,
498 properties => {
499 node => get_standard_option('pve-node'),
500 },
501 },
502 returns => {
503 type => "object",
504 description => "Result from parsing the APT repository files in /etc/apt/.",
505 properties => {
506 files => {
507 type => "array",
508 description => "List of parsed repository files.",
509 items => {
510 type => "object",
511 properties => {
512 path => {
513 type => "string",
514 description => "Path to the problematic file.",
515 },
516 'file-type' => {
517 type => "string",
518 enum => [ 'list', 'sources' ],
519 description => "Format of the file.",
520 },
521 repositories => {
522 type => "array",
523 description => "The parsed repositories.",
524 items => {
525 type => "object",
526 properties => {
527 Types => {
528 type => "array",
529 description => "List of package types.",
530 items => {
531 type => "string",
532 enum => [ 'deb', 'deb-src' ],
533 },
534 },
535 URIs => {
536 description => "List of repository URIs.",
537 type => "array",
538 items => {
539 type => "string",
540 },
541 },
542 Suites => {
543 type => "array",
544 description => "List of package distribuitions",
545 items => {
546 type => "string",
547 },
548 },
549 Components => {
550 type => "array",
551 description => "List of repository components",
552 optional => 1, # not present if suite is absolute
553 items => {
554 type => "string",
555 },
556 },
557 Options => {
558 type => "array",
559 description => "Additional options",
560 optional => 1,
561 items => {
562 type => "object",
563 properties => {
564 Key => {
565 type => "string",
566 },
567 Values => {
568 type => "array",
569 items => {
570 type => "string",
571 },
572 },
573 },
574 },
575 },
576 Comment => {
577 type => "string",
578 description => "Associated comment",
579 optional => 1,
580 },
581 FileType => {
582 type => "string",
583 enum => [ 'list', 'sources' ],
584 description => "Format of the defining file.",
585 },
586 Enabled => {
587 type => "boolean",
588 description => "Whether the repository is enabled or not",
589 },
590 },
591 },
592 },
593 digest => {
594 type => "array",
595 description => "Digest of the file as bytes.",
596 items => {
597 type => "integer",
598 },
599 },
600 },
601 },
602 },
603 errors => {
604 type => "array",
605 description => "List of problematic repository files.",
606 items => {
607 type => "object",
608 properties => {
609 path => {
610 type => "string",
611 description => "Path to the problematic file.",
612 },
613 error => {
614 type => "string",
615 description => "The error message",
616 },
617 },
618 },
619 },
620 digest => {
621 type => "string",
622 description => "Common digest of all files.",
623 },
624 infos => {
625 type => "array",
626 description => "Additional information/warnings for APT repositories.",
627 items => {
628 type => "object",
629 properties => {
630 path => {
631 type => "string",
632 description => "Path to the associated file.",
633 },
634 index => {
635 type => "string",
636 description => "Index of the associated repository within the file.",
637 },
638 property => {
639 type => "string",
640 description => "Property from which the info originates.",
641 optional => 1,
642 },
643 kind => {
644 type => "string",
645 description => "Kind of the information (e.g. warning).",
646 },
647 message => {
648 type => "string",
649 description => "Information message.",
650 }
651 },
652 },
653 },
654 'standard-repos' => {
655 type => "array",
656 description => "List of standard repositories and their configuration status",
657 items => {
658 type => "object",
659 properties => {
660 handle => {
661 type => "string",
662 description => "Handle to identify the repository.",
663 },
664 name => {
665 type => "string",
666 description => "Full name of the repository.",
667 },
668 status => {
669 type => "boolean",
670 optional => 1,
671 description => "Indicating enabled/disabled status, if the " .
672 "repository is configured.",
673 },
674 },
675 },
676 },
677 },
678 },
679 code => sub {
680 my ($param) = @_;
681
682 return Proxmox::RS::APT::Repositories::repositories("pve");
683 }});
684
685 __PACKAGE__->register_method({
686 name => 'add_repository',
687 path => 'repositories',
688 method => 'PUT',
689 description => "Add a standard repository to the configuration",
690 permissions => {
691 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
692 },
693 protected => 1,
694 proxyto => 'node',
695 parameters => {
696 additionalProperties => 0,
697 properties => {
698 node => get_standard_option('pve-node'),
699 handle => {
700 type => 'string',
701 description => "Handle that identifies a repository.",
702 },
703 digest => {
704 type => "string",
705 description => "Digest to detect modifications.",
706 maxLength => 80,
707 optional => 1,
708 },
709 },
710 },
711 returns => {
712 type => 'null',
713 },
714 code => sub {
715 my ($param) = @_;
716
717 Proxmox::RS::APT::Repositories::add_repository($param->{handle}, "pve", $param->{digest});
718 }});
719
720 __PACKAGE__->register_method({
721 name => 'change_repository',
722 path => 'repositories',
723 method => 'POST',
724 description => "Change the properties of a repository. Currently only allows enabling/disabling.",
725 permissions => {
726 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
727 },
728 protected => 1,
729 proxyto => 'node',
730 parameters => {
731 additionalProperties => 0,
732 properties => {
733 node => get_standard_option('pve-node'),
734 path => {
735 type => 'string',
736 description => "Path to the containing file.",
737 },
738 index => {
739 type => 'integer',
740 description => "Index within the file (starting from 0).",
741 },
742 enabled => {
743 type => 'boolean',
744 description => "Whether the repository should be enabled or not.",
745 optional => 1,
746 },
747 digest => {
748 type => "string",
749 description => "Digest to detect modifications.",
750 maxLength => 80,
751 optional => 1,
752 },
753 },
754 },
755 returns => {
756 type => 'null',
757 },
758 code => sub {
759 my ($param) = @_;
760
761 my $options = {};
762
763 my $enabled = $param->{enabled};
764 $options->{enabled} = int($enabled) if defined($enabled);
765
766 Proxmox::RS::APT::Repositories::change_repository(
767 $param->{path},
768 int($param->{index}),
769 $options,
770 $param->{digest}
771 );
772 }});
773
774 __PACKAGE__->register_method({
775 name => 'versions',
776 path => 'versions',
777 method => 'GET',
778 proxyto => 'node',
779 description => "Get package information for important Proxmox packages.",
780 permissions => {
781 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
782 },
783 parameters => {
784 additionalProperties => 0,
785 properties => {
786 node => get_standard_option('pve-node'),
787 },
788 },
789 returns => {
790 type => "array",
791 items => {
792 type => "object",
793 properties => {},
794 },
795 },
796 code => sub {
797 my ($param) = @_;
798
799 my $cache = &$get_apt_cache();
800 my $policy = $cache->policy;
801 my $pkgrecords = $cache->packages();
802
803 # order most important things first
804 my @list = qw(proxmox-ve pve-manager);
805
806 my $aptver = $AptPkg::System::_system->versioning();
807 my $byver = sub { $aptver->compare($cache->{$b}->{CurrentVer}->{VerStr}, $cache->{$a}->{CurrentVer}->{VerStr}) };
808 push @list, sort $byver grep { /^pve-kernel-/ && $cache->{$_}->{CurrentState} eq 'Installed' } keys %$cache;
809
810 my @opt_pack = qw(
811 ceph
812 criu
813 gfs2-utils
814 ifupdown
815 ifupdown2
816 ksm-control-daemon
817 ksmtuned
818 libpve-apiclient-perl
819 libpve-network-perl
820 openvswitch-switch
821 proxmox-backup-file-restore
822 proxmox-offline-mirror-helper
823 pve-zsync
824 zfsutils-linux
825 );
826
827 my @pkgs = qw(
828 ceph-fuse
829 corosync
830 libjs-extjs
831 glusterfs-client
832 libknet1
833 libpve-access-control
834 libpve-common-perl
835 libpve-guest-common-perl
836 libpve-http-server-perl
837 libpve-storage-perl
838 libproxmox-acme-perl
839 libproxmox-backup-qemu0
840 libqb0
841 libspice-server1
842 lvm2
843 lxc-pve
844 lxcfs
845 novnc-pve
846 proxmox-backup-client
847 proxmox-mini-journalreader
848 proxmox-widget-toolkit
849 pve-cluster
850 pve-container
851 pve-docs
852 pve-edk2-firmware
853 pve-firewall
854 pve-firmware
855 pve-ha-manager
856 pve-i18n
857 pve-qemu-kvm
858 pve-xtermjs
859 qemu-server
860 smartmontools
861 swtpm
862 spiceterm
863 vncterm
864 );
865
866 # add the rest ordered by name, easier to find for humans
867 push @list, (sort @pkgs, @opt_pack);
868
869 my (undef, undef, $kernel_release) = POSIX::uname();
870 my $pvever = PVE::pvecfg::version_text();
871
872 my $pkglist = [];
873 foreach my $pkgname (@list) {
874 my $p = $cache->{$pkgname};
875 my $info = $pkgrecords->lookup($pkgname);
876 my $candidate_ver = defined($p) ? $policy->candidate($p) : undef;
877 my $res;
878 if (my $current_ver = $p->{CurrentVer}) {
879 $res = $assemble_pkginfo->($pkgname, $info, $current_ver, $candidate_ver || $current_ver);
880 } elsif ($candidate_ver) {
881 $res = $assemble_pkginfo->($pkgname, $info, $candidate_ver, $candidate_ver);
882 delete $res->{OldVersion};
883 } else {
884 next;
885 }
886 $res->{CurrentState} = $p->{CurrentState};
887
888 # hack: add some useful information (used by 'pveversion -v')
889 if ($pkgname eq 'pve-manager') {
890 $res->{ManagerVersion} = $pvever;
891 } elsif ($pkgname eq 'proxmox-ve') {
892 $res->{RunningKernel} = $kernel_release;
893 }
894 if (grep( /^$pkgname$/, @opt_pack)) {
895 next if $res->{CurrentState} eq 'NotInstalled';
896 }
897
898 push @$pkglist, $res;
899 }
900
901 return $pkglist;
902 }});
903
904 1;