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