]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/APT.pm
add reference to git version
[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';
179 my $current_ver = $p->{CurrentVer};
180 my $candidate_ver = $policy->candidate($p);
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 },
395 parameters => {
396 additionalProperties => 0,
397 properties => {
398 node => get_standard_option('pve-node'),
399 name => {
400 description => "Package name.",
401 type => 'string',
402 },
403 version => {
404 description => "Package version.",
405 type => 'string',
406 optional => 1,
407 },
408 },
409 },
410 returns => {
411 type => "string",
412 },
413 code => sub {
414 my ($param) = @_;
415
416 my $pkgname = $param->{name};
417
418 my $cache = &$get_apt_cache();
419 my $policy = $cache->policy;
420 my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n";
421 my $pkgrecords = $cache->packages();
422
423 my $ver;
424 if ($param->{version}) {
425 if (my $available = $p->{VersionList}) {
426 for my $v (@$available) {
427 if ($v->{VerStr} eq $param->{version}) {
428 $ver = $v;
429 last;
430 }
431 }
432 }
433 die "package '$pkgname' version '$param->{version}' is not avalable\n" if !$ver;
434 } else {
435 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
436 }
437
438 my $info = $pkgrecords->lookup($pkgname);
439
a972b69d
DM
440 my $pkgfile = &$get_pkgfile($ver);
441 my $url;
442
443 die "changelog for '${pkgname}_$ver->{VerStr}' not available\n"
2ba6d822 444 if !($pkgfile && ($url = &$get_changelog_url($pkgname, $info, $ver->{VerStr}, $pkgfile->{Origin}, $pkgfile->{Component})));
b688d438
DM
445
446 my $data = "";
447
f5ed75de
DM
448 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
449 my $proxy = $dccfg->{http_proxy};
450
451 my $ua = LWP::UserAgent->new;
452 $ua->agent("PVE/1.0");
453 $ua->timeout(10);
454 $ua->max_size(1024*1024);
2ba6d822
DM
455 $ua->ssl_opts(verify_hostname => 0); # don't care for changelogs
456
f5ed75de 457 if ($proxy) {
2ba6d822 458 $ua->proxy(['http', 'https'], $proxy);
f5ed75de
DM
459 } else {
460 $ua->env_proxy;
461 }
462
2ba6d822
DM
463 my $username;
464 my $pw;
465
466 if ($pkgfile->{Origin} eq 'Proxmox' && $pkgfile->{Component} eq 'pve-enterprise') {
467 my $info = PVE::INotify::read_file('subscription');
468 if ($info->{status} eq 'Active') {
469 $username = $info->{key};
470 $pw = PVE::API2Tools::get_hwaddress();
471 $ua->credentials("enterprise.proxmox.com:443", 'pve-enterprise-repository',
472 $username, $pw);
473 }
474 }
475
f5ed75de 476 my $response = $ua->get($url);
b688d438 477
f5ed75de
DM
478 if ($response->is_success) {
479 $data = $response->decoded_content;
480 } else {
396c9e4a 481 PVE::Exception::raise($response->message, code => $response->code);
f5ed75de 482 }
b688d438
DM
483
484 return $data;
485 }});
486
8794761f
DM
487__PACKAGE__->register_method({
488 name => 'versions',
489 path => 'versions',
490 method => 'GET',
491 description => "Get package information for important Proxmox packages.",
492 permissions => {
493 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
494 },
495 parameters => {
496 additionalProperties => 0,
497 properties => {
498 node => get_standard_option('pve-node'),
499 },
500 },
501 returns => {
502 type => "array",
503 items => {
504 type => "object",
505 properties => {},
506 },
507 },
508 code => sub {
509 my ($param) = @_;
510
511 my $pkgname = $param->{name};
512
513 my $cache = &$get_apt_cache();
514 my $policy = $cache->policy;
515 my $pkgrecords = $cache->packages();
516
517 # try to use a resonable ordering (most important things first)
518 my @list = qw(proxmox-ve-2.6.32 pve-manager);
519
520 foreach my $pkgname (keys %$cache) {
521 if ($pkgname =~ m/pve-kernel-/) {
522 my $p = $cache->{$pkgname};
523 push @list, $pkgname if $p && $p->{CurrentState} eq 'Installed';
524 }
525 }
526
d32d05d6 527 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
528
529 my $pkglist = [];
530
531 my (undef, undef, $kernel_release) = POSIX::uname();
532 my $pvever = PVE::pvecfg::version_text();
533
534 foreach my $pkgname (@list) {
535 my $p = $cache->{$pkgname};
536 my $info = $pkgrecords->lookup($pkgname);
537 my $candidate_ver = $policy->candidate($p);
538 my $res;
539 if (my $current_ver = $p->{CurrentVer}) {
540 $res = &$assemble_pkginfo($pkgname, $info, $current_ver,
541 $candidate_ver || $current_ver);
542 } elsif ($candidate_ver) {
543 $res = &$assemble_pkginfo($pkgname, $info, $candidate_ver,
544 $candidate_ver);
545 delete $res->{OldVersion};
546 } else {
547 next;
548 }
549 $res->{CurrentState} = $p->{CurrentState};
550
551 # hack: add some useful information (used by 'pveversion -v')
552 if ($pkgname eq 'pve-manager') {
553 $res->{ManagerVersion} = $pvever;
554 } elsif ($pkgname eq 'proxmox-ve-2.6.32') {
555 $res->{RunningKernel} = $kernel_release;
556 }
557
558 push @$pkglist, $res;
559 }
560
561 return $pkglist;
562 }});
563
21299915 5641;