]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/APT.pm
pveversion: also list lxcfs and cgmanager
[pve-manager.git] / PVE / API2 / APT.pm
CommitLineData
21299915
DM
1package PVE::API2::APT;
2
3use strict;
4use warnings;
8794761f
DM
5
6use POSIX;
cd0bc36b 7use File::stat ();
a88002cf 8use IO::File;
d330e26f 9use File::Basename;
21299915 10
f5ed75de
DM
11use LWP::UserAgent;
12
8794761f 13use PVE::pvecfg;
21299915 14use PVE::Tools qw(extract_param);
f5ed75de 15use PVE::Cluster;
21299915
DM
16use PVE::SafeSyslog;
17use PVE::INotify;
396c9e4a 18use PVE::Exception;
21299915
DM
19use PVE::RESTHandler;
20use PVE::RPCEnvironment;
446b9669 21use PVE::API2Tools;
21299915 22
cd0bc36b 23use JSON;
21299915
DM
24use PVE::JSONSchema qw(get_standard_option);
25
26use AptPkg::Cache;
27use AptPkg::Version;
28use AptPkg::PkgRecords;
29
21299915
DM
30my $get_apt_cache = sub {
31
c06e9cc8 32 my $apt_cache = AptPkg::Cache->new() || die "unable to initialize AptPkg::Cache\n";
21299915
DM
33
34 return $apt_cache;
35};
36
37use base qw(PVE::RESTHandler);
38
39__PACKAGE__->register_method({
40 name => 'index',
41 path => '',
42 method => 'GET',
43 description => "Directory index for apt (Advanced Package Tool).",
44 permissions => {
45 user => 'all',
46 },
47 parameters => {
48 additionalProperties => 0,
49 properties => {
50 node => get_standard_option('pve-node'),
51 },
52 },
53 returns => {
54 type => "array",
55 items => {
56 type => "object",
57 properties => {
58 id => { type => 'string' },
59 },
60 },
61 links => [ { rel => 'child', href => "{id}" } ],
62 },
63 code => sub {
64 my ($param) = @_;
65
66 my $res = [
21299915 67 { id => 'changelog' },
8794761f
DM
68 { id => 'update' },
69 { id => 'versions' },
21299915
DM
70 ];
71
72 return $res;
73 }});
74
a972b69d
DM
75my $get_pkgfile = sub {
76 my ($veriter) = @_;
21299915 77
a972b69d 78 foreach my $verfile (@{$veriter->{FileList}}) {
00d48356 79 my $pkgfile = $verfile->{File};
a972b69d
DM
80 next if !$pkgfile->{Origin};
81 return $pkgfile;
82 }
83
84 return undef;
85};
86
87my $get_changelog_url =sub {
2ba6d822 88 my ($pkgname, $info, $pkgver, $origin, $component) = @_;
a972b69d
DM
89
90 my $changelog_url;
91 my $base = dirname($info->{FileName});
92 if ($origin && $base) {
93 $pkgver =~ s/^\d+://; # strip epoch
94 my $srcpkg = $info->{SourcePkg} || $pkgname;
95 if ($origin eq 'Debian') {
702ecae1 96 $base =~ s!pool/updates/!pool/!; # for security channel
a972b69d 97 $changelog_url = "http://packages.debian.org/changelogs/$base/" .
09c67ebb
DM
98 "${srcpkg}_${pkgver}/changelog";
99 } elsif ($origin eq 'Proxmox') {
2ba6d822
DM
100 if ($component eq 'pve-enterprise') {
101 $changelog_url = "https://enterprise.proxmox.com/debian/$base/" .
8fd7134c 102 "${pkgname}_${pkgver}.changelog";
2ba6d822
DM
103 } else {
104 $changelog_url = "http://download.proxmox.com/debian/$base/" .
8fd7134c 105 "${pkgname}_${pkgver}.changelog";
2ba6d822 106 }
00d48356
DM
107 }
108 }
a972b69d 109
b688d438
DM
110 return $changelog_url;
111};
112
113my $assemble_pkginfo = sub {
114 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
00d48356 115
21299915
DM
116 my $data = {
117 Package => $info->{Name},
118 Title => $info->{ShortDesc},
a972b69d 119 Origin => 'unknown',
21299915
DM
120 };
121
a972b69d 122 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
2ba6d822
DM
123 $data->{Origin} = $pkgfile->{Origin};
124 if (my $changelog_url = &$get_changelog_url($pkgname, $info, $candidate_ver->{VerStr},
125 $pkgfile->{Origin}, $pkgfile->{Component})) {
a972b69d
DM
126 $data->{ChangeLogUrl} = $changelog_url;
127 }
b688d438 128 }
00d48356 129
21299915
DM
130 if (my $desc = $info->{LongDesc}) {
131 $desc =~ s/^.*\n\s?//; # remove first line
132 $desc =~ s/\n / /g;
133 $data->{Description} = $desc;
134 }
135
136 foreach my $k (qw(Section Arch Priority)) {
137 $data->{$k} = $candidate_ver->{$k};
138 }
139
140 $data->{Version} = $candidate_ver->{VerStr};
93305404 141 $data->{OldVersion} = $current_ver->{VerStr} if $current_ver;
21299915
DM
142
143 return $data;
144};
145
4806bc69
DM
146# we try to cache results
147my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates";
148
745d942d
DM
149my $read_cached_pkgstatus = sub {
150 my $data = [];
151 eval {
152 my $jsonstr = PVE::Tools::file_get_contents($pve_pkgstatus_fn, 5*1024*1024);
153 $data = decode_json($jsonstr);
154 };
155 if (my $err = $@) {
156 warn "error reading cached package status in $pve_pkgstatus_fn\n";
157 }
158 return $data;
159};
160
4806bc69
DM
161my $update_pve_pkgstatus = sub {
162
446f9217
DM
163 syslog('info', "update new package list: $pve_pkgstatus_fn");
164
745d942d
DM
165 my $notify_status = {};
166 my $oldpkglist = &$read_cached_pkgstatus();
167 foreach my $pi (@$oldpkglist) {
d97a4468 168 $notify_status->{$pi->{Package}} = $pi->{NotifyStatus};
745d942d
DM
169 }
170
4806bc69
DM
171 my $pkglist = [];
172
173 my $cache = &$get_apt_cache();
174 my $policy = $cache->policy;
175 my $pkgrecords = $cache->packages();
176
177 foreach my $pkgname (keys %$cache) {
178 my $p = $cache->{$pkgname};
2051816c 179 next if !$p->{SelectedState} || ($p->{SelectedState} ne 'Install');
26f876ce
DM
180 my $current_ver = $p->{CurrentVer} || next;
181 my $candidate_ver = $policy->candidate($p) || next;
4806bc69 182
396c9e4a 183 if ($current_ver->{VerStr} ne $candidate_ver->{VerStr}) {
4806bc69
DM
184 my $info = $pkgrecords->lookup($pkgname);
185 my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver);
186 push @$pkglist, $res;
93305404
DM
187
188 # also check if we need any new package
189 # Note: this is just a quick hack (not recursive as it should be), because
190 # I found no way to get that info from AptPkg
191 if (my $deps = $candidate_ver->{DependsList}) {
192 my $found;
193 my $req;
194 for my $d (@$deps) {
195 if ($d->{DepType} eq 'Depends') {
196 $found = $d->{TargetPkg}->{SelectedState} eq 'Install' if !$found;
197 $req = $d->{TargetPkg} if !$req;
198
199 if (!($d->{CompType} & AptPkg::Dep::Or)) {
200 if (!$found && $req) { # New required Package
201 my $tpname = $req->{Name};
202 my $tpinfo = $pkgrecords->lookup($tpname);
203 my $tpcv = $policy->candidate($req);
204 if ($tpinfo && $tpcv) {
205 my $res = &$assemble_pkginfo($tpname, $tpinfo, undef, $tpcv);
206 push @$pkglist, $res;
207 }
208 }
209 undef $found;
210 undef $req;
211 }
212 }
213 }
214 }
4806bc69
DM
215 }
216 }
745d942d
DM
217
218 # keep notification status (avoid sending mails abou new packages more than once)
219 foreach my $pi (@$pkglist) {
220 if (my $ns = $notify_status->{$pi->{Package}}) {
221 $pi->{NotifyStatus} = $ns if $ns eq $pi->{Version};
222 }
223 }
224
4806bc69
DM
225 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
226
227 return $pkglist;
228};
229
21299915
DM
230__PACKAGE__->register_method({
231 name => 'list_updates',
232 path => 'update',
233 method => 'GET',
234 description => "List available updates.",
235 permissions => {
236 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
237 },
238 protected => 1,
239 proxyto => 'node',
240 parameters => {
241 additionalProperties => 0,
242 properties => {
243 node => get_standard_option('pve-node'),
244 },
245 },
246 returns => {
247 type => "array",
248 items => {
249 type => "object",
250 properties => {},
251 },
252 },
253 code => sub {
254 my ($param) = @_;
255
cd0bc36b
DM
256 if (my $st1 = File::stat::stat($pve_pkgstatus_fn)) {
257 my $st2 = File::stat::stat("/var/cache/apt/pkgcache.bin");
258 my $st3 = File::stat::stat("/var/lib/dpkg/status");
259
446f9217 260 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
745d942d 261 if (my $data = &$read_cached_pkgstatus()) {
cd0bc36b
DM
262 return $data;
263 }
264 }
265 }
266
4806bc69
DM
267 my $pkglist = &$update_pve_pkgstatus();
268
269 return $pkglist;
270 }});
271
272__PACKAGE__->register_method({
273 name => 'update_database',
274 path => 'update',
275 method => 'POST',
276 description => "This is used to resynchronize the package index files from their sources (apt-get update).",
277 permissions => {
278 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
279 },
280 protected => 1,
281 proxyto => 'node',
282 parameters => {
283 additionalProperties => 0,
284 properties => {
285 node => get_standard_option('pve-node'),
a88002cf
DM
286 notify => {
287 type => 'boolean',
288 description => "Send notification mail about new packages (to email address specified for user 'root\@pam').",
289 optional => 1,
290 default => 0,
291 },
2544e8d5
DM
292 quiet => {
293 type => 'boolean',
294 description => "Only produces output suitable for logging, omitting progress indicators.",
295 optional => 1,
296 default => 0,
297 },
4806bc69
DM
298 },
299 },
300 returns => {
301 type => 'string',
302 },
303 code => sub {
304 my ($param) = @_;
305
306 my $rpcenv = PVE::RPCEnvironment::get();
21299915 307
4806bc69 308 my $authuser = $rpcenv->get_user();
21299915 309
4806bc69
DM
310 my $realcmd = sub {
311 my $upid = shift;
21299915 312
19ed44c0
DM
313 # setup proxy for apt
314 my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
315
316 my $aptconf = "// no proxy configured\n";
317 if ($dcconf->{http_proxy}) {
318 $aptconf = "Acquire::http::Proxy \"$dcconf->{http_proxy}\";\n";
319 }
320 my $aptcfn = "/etc/apt/apt.conf.d/76pveproxy";
321 PVE::Tools::file_set_contents($aptcfn, $aptconf);
322
4806bc69 323 my $cmd = ['apt-get', 'update'];
21299915 324
a137235a 325 print "starting apt-get update\n" if !$param->{quiet};
4806bc69 326
f460dc12
DM
327 if ($param->{quiet}) {
328 PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {});
329 } else {
330 PVE::Tools::run_command($cmd);
331 }
4806bc69 332
a88002cf
DM
333 my $pkglist = &$update_pve_pkgstatus();
334
335 if ($param->{notify} && scalar(@$pkglist)) {
336
337 my $usercfg = PVE::Cluster::cfs_read_file("user.cfg");
338 my $rootcfg = $usercfg->{users}->{'root@pam'} || {};
339 my $mailto = $rootcfg->{email};
340
341 if ($mailto) {
342 my $hostname = `hostname -f` || PVE::INotify::nodename();
343 chomp $hostname;
4b152656 344 my $mailfrom = $dcconf->{email_from} || "root";
a88002cf
DM
345
346 my $data = "Content-Type: text/plain;charset=\"UTF8\"\n";
347 $data .= "Content-Transfer-Encoding: 8bit\n";
4b152656 348 $data .= "FROM: <$mailfrom>\n";
a88002cf
DM
349 $data .= "TO: $mailto\n";
350 $data .= "SUBJECT: New software packages available ($hostname)\n";
351 $data .= "\n";
352
353 $data .= "The following updates are available:\n\n";
354
d97a4468 355 my $count = 0;
a88002cf 356 foreach my $p (sort {$a->{Package} cmp $b->{Package} } @$pkglist) {
d97a4468
DM
357 next if $p->{NotifyStatus} && $p->{NotifyStatus} eq $p->{Version};
358 $count++;
93305404
DM
359 if ($p->{OldVersion}) {
360 $data .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
361 } else {
362 $data .= "$p->{Package}: $p->{Version} (new)\n";
363 }
a88002cf
DM
364 }
365
d97a4468
DM
366 return if !$count;
367
4b152656 368 my $fh = IO::File->new("|sendmail -B 8BITMIME -f $mailfrom $mailto") ||
a88002cf
DM
369 die "unable to open 'sendmail' - $!";
370
371 print $fh $data;
372
745d942d
DM
373 $fh->close() || die "unable to close 'sendmail' - $!";
374
375 foreach my $pi (@$pkglist) {
376 $pi->{NotifyStatus} = $pi->{Version};
377 }
378 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
a88002cf
DM
379 }
380 }
4806bc69
DM
381
382 return;
383 };
384
c2d3fbe0 385 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
cd0bc36b 386
21299915
DM
387 }});
388
b688d438
DM
389__PACKAGE__->register_method({
390 name => 'changelog',
391 path => 'changelog',
392 method => 'GET',
393 description => "Get package changelogs.",
394 permissions => {
395 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
396 },
a25fa6e9 397 proxyto => 'node',
b688d438
DM
398 parameters => {
399 additionalProperties => 0,
400 properties => {
401 node => get_standard_option('pve-node'),
402 name => {
403 description => "Package name.",
404 type => 'string',
405 },
406 version => {
407 description => "Package version.",
408 type => 'string',
409 optional => 1,
410 },
411 },
412 },
413 returns => {
414 type => "string",
415 },
416 code => sub {
417 my ($param) = @_;
418
419 my $pkgname = $param->{name};
420
421 my $cache = &$get_apt_cache();
422 my $policy = $cache->policy;
423 my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n";
424 my $pkgrecords = $cache->packages();
425
426 my $ver;
427 if ($param->{version}) {
428 if (my $available = $p->{VersionList}) {
429 for my $v (@$available) {
430 if ($v->{VerStr} eq $param->{version}) {
431 $ver = $v;
432 last;
433 }
434 }
435 }
436 die "package '$pkgname' version '$param->{version}' is not avalable\n" if !$ver;
437 } else {
438 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
439 }
440
441 my $info = $pkgrecords->lookup($pkgname);
442
a972b69d
DM
443 my $pkgfile = &$get_pkgfile($ver);
444 my $url;
445
446 die "changelog for '${pkgname}_$ver->{VerStr}' not available\n"
2ba6d822 447 if !($pkgfile && ($url = &$get_changelog_url($pkgname, $info, $ver->{VerStr}, $pkgfile->{Origin}, $pkgfile->{Component})));
b688d438
DM
448
449 my $data = "";
450
f5ed75de
DM
451 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
452 my $proxy = $dccfg->{http_proxy};
453
454 my $ua = LWP::UserAgent->new;
455 $ua->agent("PVE/1.0");
456 $ua->timeout(10);
457 $ua->max_size(1024*1024);
2ba6d822
DM
458 $ua->ssl_opts(verify_hostname => 0); # don't care for changelogs
459
446b9669
DM
460 # HACK: LWP does not use proxy 'CONNECT' for https
461 local $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS} = "Net::SSL";
462 local ($ENV{HTTPS_PROXY}, $ENV{HTTPS_PROXY_USERNAME}, $ENV{HTTPS_PROXY_PASSWORD});
463
f5ed75de 464 if ($proxy) {
446b9669
DM
465 ($ENV{HTTPS_PROXY}, $ENV{HTTPS_PROXY_USERNAME}, $ENV{HTTPS_PROXY_PASSWORD}) =
466 PVE::API2Tools::parse_http_proxy($proxy);
467 $ua->proxy(['http'], $proxy);
f5ed75de
DM
468 } else {
469 $ua->env_proxy;
470 }
471
2ba6d822
DM
472 my $username;
473 my $pw;
474
475 if ($pkgfile->{Origin} eq 'Proxmox' && $pkgfile->{Component} eq 'pve-enterprise') {
476 my $info = PVE::INotify::read_file('subscription');
477 if ($info->{status} eq 'Active') {
478 $username = $info->{key};
479 $pw = PVE::API2Tools::get_hwaddress();
480 $ua->credentials("enterprise.proxmox.com:443", 'pve-enterprise-repository',
481 $username, $pw);
482 }
483 }
484
f5ed75de 485 my $response = $ua->get($url);
b688d438 486
f5ed75de
DM
487 if ($response->is_success) {
488 $data = $response->decoded_content;
489 } else {
396c9e4a 490 PVE::Exception::raise($response->message, code => $response->code);
f5ed75de 491 }
b688d438
DM
492
493 return $data;
494 }});
495
8794761f
DM
496__PACKAGE__->register_method({
497 name => 'versions',
498 path => 'versions',
499 method => 'GET',
500 description => "Get package information for important Proxmox packages.",
501 permissions => {
502 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
503 },
504 parameters => {
505 additionalProperties => 0,
506 properties => {
507 node => get_standard_option('pve-node'),
508 },
509 },
510 returns => {
511 type => "array",
512 items => {
513 type => "object",
514 properties => {},
515 },
516 },
517 code => sub {
518 my ($param) = @_;
519
520 my $pkgname = $param->{name};
521
522 my $cache = &$get_apt_cache();
523 my $policy = $cache->policy;
524 my $pkgrecords = $cache->packages();
525
526 # try to use a resonable ordering (most important things first)
97f6e029 527 my @list = qw(proxmox-ve pve-manager);
8794761f
DM
528
529 foreach my $pkgname (keys %$cache) {
530 if ($pkgname =~ m/pve-kernel-/) {
531 my $p = $cache->{$pkgname};
532 push @list, $pkgname if $p && $p->{CurrentState} eq 'Installed';
533 }
534 }
535
97f6e029
DM
536 # comment out old packages uses before 4.0
537 # resource-agents-pve fence-agents-pve vzctl vzprocps vzquota
538
21ccef4f 539 push @list, qw(lvm2 clvm corosync-pve libqb0 pve-cluster qemu-server pve-firmware libpve-common-perl libpve-access-control libpve-storage-perl pve-libspice-server1 vncterm pve-qemu-kvm pve-container pve-firewall pve-ha-manager ksm-control-daemon glusterfs-client lxc-pve lxcfs cgmanager);
8794761f
DM
540
541 my $pkglist = [];
542
543 my (undef, undef, $kernel_release) = POSIX::uname();
544 my $pvever = PVE::pvecfg::version_text();
545
546 foreach my $pkgname (@list) {
547 my $p = $cache->{$pkgname};
548 my $info = $pkgrecords->lookup($pkgname);
c0d696f9 549 my $candidate_ver = defined($p) ? $policy->candidate($p) : undef;
8794761f
DM
550 my $res;
551 if (my $current_ver = $p->{CurrentVer}) {
552 $res = &$assemble_pkginfo($pkgname, $info, $current_ver,
553 $candidate_ver || $current_ver);
554 } elsif ($candidate_ver) {
555 $res = &$assemble_pkginfo($pkgname, $info, $candidate_ver,
556 $candidate_ver);
557 delete $res->{OldVersion};
558 } else {
559 next;
560 }
561 $res->{CurrentState} = $p->{CurrentState};
562
563 # hack: add some useful information (used by 'pveversion -v')
564 if ($pkgname eq 'pve-manager') {
565 $res->{ManagerVersion} = $pvever;
97f6e029 566 } elsif ($pkgname eq 'proxmox-ve') {
8794761f
DM
567 $res->{RunningKernel} = $kernel_release;
568 }
569
570 push @$pkglist, $res;
571 }
572
573 return $pkglist;
574 }});
575
21299915 5761;