]> git.proxmox.com Git - pve-manager-legacy.git/blame - PVE/API2/APT.pm
run 'apt-get update' in quiet mode when starting from cron
[pve-manager-legacy.git] / PVE / API2 / APT.pm
CommitLineData
8863ae95
DM
1package PVE::API2::APT;
2
3use strict;
4use warnings;
8c1d3a4e
DM
5
6use POSIX;
92ebf7e0 7use File::stat ();
1056f33a 8use IO::File;
9aac6297 9use File::Basename;
8863ae95 10
59c8caa9
DM
11use LWP::UserAgent;
12
8c1d3a4e 13use PVE::pvecfg;
8863ae95 14use PVE::Tools qw(extract_param);
59c8caa9 15use PVE::Cluster;
8863ae95
DM
16use PVE::SafeSyslog;
17use PVE::INotify;
d3ce3535 18use PVE::Exception;
8863ae95
DM
19use PVE::RESTHandler;
20use PVE::RPCEnvironment;
21
92ebf7e0 22use JSON;
8863ae95
DM
23use PVE::JSONSchema qw(get_standard_option);
24
25use AptPkg::Cache;
26use AptPkg::Version;
27use AptPkg::PkgRecords;
28
8863ae95
DM
29my $get_apt_cache = sub {
30
1163ee63 31 my $apt_cache = AptPkg::Cache->new() || die "unable to initialize AptPkg::Cache\n";
8863ae95
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 = [
8863ae95 66 { id => 'changelog' },
8c1d3a4e
DM
67 { id => 'update' },
68 { id => 'versions' },
8863ae95
DM
69 ];
70
71 return $res;
72 }});
73
03d19bdc
DM
74my $get_pkgfile = sub {
75 my ($veriter) = @_;
8863ae95 76
03d19bdc 77 foreach my $verfile (@{$veriter->{FileList}}) {
5be32a77 78 my $pkgfile = $verfile->{File};
03d19bdc
DM
79 next if !$pkgfile->{Origin};
80 return $pkgfile;
81 }
82
83 return undef;
84};
85
86my $get_changelog_url =sub {
dab54677 87 my ($pkgname, $info, $pkgver, $origin, $component) = @_;
03d19bdc
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') {
95 $changelog_url = "http://packages.debian.org/changelogs/$base/" .
6dc3c5c5
DM
96 "${srcpkg}_${pkgver}/changelog";
97 } elsif ($origin eq 'Proxmox') {
dab54677
DM
98 if ($component eq 'pve-enterprise') {
99 $changelog_url = "https://enterprise.proxmox.com/debian/$base/" .
100 "${srcpkg}_${pkgver}.changelog";
101 } else {
102 $changelog_url = "http://download.proxmox.com/debian/$base/" .
103 "${srcpkg}_${pkgver}.changelog";
104 }
5be32a77
DM
105 }
106 }
03d19bdc 107
6916396a
DM
108 return $changelog_url;
109};
110
111my $assemble_pkginfo = sub {
112 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
5be32a77 113
8863ae95
DM
114 my $data = {
115 Package => $info->{Name},
116 Title => $info->{ShortDesc},
03d19bdc 117 Origin => 'unknown',
8863ae95
DM
118 };
119
03d19bdc 120 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
dab54677
DM
121 $data->{Origin} = $pkgfile->{Origin};
122 if (my $changelog_url = &$get_changelog_url($pkgname, $info, $candidate_ver->{VerStr},
123 $pkgfile->{Origin}, $pkgfile->{Component})) {
03d19bdc
DM
124 $data->{ChangeLogUrl} = $changelog_url;
125 }
6916396a 126 }
5be32a77 127
8863ae95
DM
128 if (my $desc = $info->{LongDesc}) {
129 $desc =~ s/^.*\n\s?//; # remove first line
130 $desc =~ s/\n / /g;
131 $data->{Description} = $desc;
132 }
133
134 foreach my $k (qw(Section Arch Priority)) {
135 $data->{$k} = $candidate_ver->{$k};
136 }
137
138 $data->{Version} = $candidate_ver->{VerStr};
139 $data->{OldVersion} = $current_ver->{VerStr};
140
141 return $data;
142};
143
690e72cd
DM
144# we try to cache results
145my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates";
146
147my $update_pve_pkgstatus = sub {
148
7cb07c3d
DM
149 syslog('info', "update new package list: $pve_pkgstatus_fn");
150
690e72cd
DM
151 my $pkglist = [];
152
153 my $cache = &$get_apt_cache();
154 my $policy = $cache->policy;
155 my $pkgrecords = $cache->packages();
156
157 foreach my $pkgname (keys %$cache) {
158 my $p = $cache->{$pkgname};
159 next if $p->{SelectedState} ne 'Install';
160 my $current_ver = $p->{CurrentVer};
161 my $candidate_ver = $policy->candidate($p);
162
d3ce3535 163 if ($current_ver->{VerStr} ne $candidate_ver->{VerStr}) {
690e72cd
DM
164 my $info = $pkgrecords->lookup($pkgname);
165 my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver);
166 push @$pkglist, $res;
167 }
168 }
169
170 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
171
172 return $pkglist;
173};
174
8863ae95
DM
175__PACKAGE__->register_method({
176 name => 'list_updates',
177 path => 'update',
178 method => 'GET',
179 description => "List available updates.",
180 permissions => {
181 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
182 },
183 protected => 1,
184 proxyto => 'node',
185 parameters => {
186 additionalProperties => 0,
187 properties => {
188 node => get_standard_option('pve-node'),
189 },
190 },
191 returns => {
192 type => "array",
193 items => {
194 type => "object",
195 properties => {},
196 },
197 },
198 code => sub {
199 my ($param) = @_;
200
92ebf7e0
DM
201 if (my $st1 = File::stat::stat($pve_pkgstatus_fn)) {
202 my $st2 = File::stat::stat("/var/cache/apt/pkgcache.bin");
203 my $st3 = File::stat::stat("/var/lib/dpkg/status");
204
7cb07c3d 205 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
92ebf7e0
DM
206 my $data;
207 eval {
208 my $jsonstr = PVE::Tools::file_get_contents($pve_pkgstatus_fn, 5*1024*1024);
209 $data = decode_json($jsonstr);
210 };
211 if (my $err = $@) {
212 warn "error readin cached package status in $pve_pkgstatus_fn\n";
213 # continue and overwrite cache with new content
214 } else {
215 return $data;
216 }
217 }
218 }
219
690e72cd
DM
220 my $pkglist = &$update_pve_pkgstatus();
221
222 return $pkglist;
223 }});
224
225__PACKAGE__->register_method({
226 name => 'update_database',
227 path => 'update',
228 method => 'POST',
229 description => "This is used to resynchronize the package index files from their sources (apt-get update).",
230 permissions => {
231 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
232 },
233 protected => 1,
234 proxyto => 'node',
235 parameters => {
236 additionalProperties => 0,
237 properties => {
238 node => get_standard_option('pve-node'),
1056f33a
DM
239 notify => {
240 type => 'boolean',
241 description => "Send notification mail about new packages (to email address specified for user 'root\@pam').",
242 optional => 1,
243 default => 0,
244 },
8c438494
DM
245 quiet => {
246 type => 'boolean',
247 description => "Only produces output suitable for logging, omitting progress indicators.",
248 optional => 1,
249 default => 0,
250 },
690e72cd
DM
251 },
252 },
253 returns => {
254 type => 'string',
255 },
256 code => sub {
257 my ($param) = @_;
258
259 my $rpcenv = PVE::RPCEnvironment::get();
8863ae95 260
690e72cd 261 my $authuser = $rpcenv->get_user();
8863ae95 262
690e72cd
DM
263 my $realcmd = sub {
264 my $upid = shift;
8863ae95 265
39d729a6
DM
266 # setup proxy for apt
267 my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
268
269 my $aptconf = "// no proxy configured\n";
270 if ($dcconf->{http_proxy}) {
271 $aptconf = "Acquire::http::Proxy \"$dcconf->{http_proxy}\";\n";
272 }
273 my $aptcfn = "/etc/apt/apt.conf.d/76pveproxy";
274 PVE::Tools::file_set_contents($aptcfn, $aptconf);
275
690e72cd 276 my $cmd = ['apt-get', 'update'];
8863ae95 277
8c438494
DM
278 push @$cmd, '-qq' if $param->{quiet};
279
690e72cd
DM
280 print "starting apt-get update\n";
281
282 PVE::Tools::run_command($cmd);
283
1056f33a
DM
284 my $pkglist = &$update_pve_pkgstatus();
285
286 if ($param->{notify} && scalar(@$pkglist)) {
287
288 my $usercfg = PVE::Cluster::cfs_read_file("user.cfg");
289 my $rootcfg = $usercfg->{users}->{'root@pam'} || {};
290 my $mailto = $rootcfg->{email};
291
292 if ($mailto) {
293 my $hostname = `hostname -f` || PVE::INotify::nodename();
294 chomp $hostname;
295
296 my $data = "Content-Type: text/plain;charset=\"UTF8\"\n";
297 $data .= "Content-Transfer-Encoding: 8bit\n";
298 $data .= "FROM: <root\@$hostname>\n";
299 $data .= "TO: $mailto\n";
300 $data .= "SUBJECT: New software packages available ($hostname)\n";
301 $data .= "\n";
302
303 $data .= "The following updates are available:\n\n";
304
305 foreach my $p (sort {$a->{Package} cmp $b->{Package} } @$pkglist) {
306 $data .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
307 }
308
309 my $fh = IO::File->new("|sendmail -B 8BITMIME $mailto") ||
310 die "unable to open 'sendmail' - $!";
311
312 print $fh $data;
313
314 $fh->close();
315 }
316 }
690e72cd
DM
317
318 return;
319 };
320
6c7dc579 321 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
92ebf7e0 322
8863ae95
DM
323 }});
324
6916396a
DM
325__PACKAGE__->register_method({
326 name => 'changelog',
327 path => 'changelog',
328 method => 'GET',
329 description => "Get package changelogs.",
330 permissions => {
331 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
332 },
333 parameters => {
334 additionalProperties => 0,
335 properties => {
336 node => get_standard_option('pve-node'),
337 name => {
338 description => "Package name.",
339 type => 'string',
340 },
341 version => {
342 description => "Package version.",
343 type => 'string',
344 optional => 1,
345 },
346 },
347 },
348 returns => {
349 type => "string",
350 },
351 code => sub {
352 my ($param) = @_;
353
354 my $pkgname = $param->{name};
355
356 my $cache = &$get_apt_cache();
357 my $policy = $cache->policy;
358 my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n";
359 my $pkgrecords = $cache->packages();
360
361 my $ver;
362 if ($param->{version}) {
363 if (my $available = $p->{VersionList}) {
364 for my $v (@$available) {
365 if ($v->{VerStr} eq $param->{version}) {
366 $ver = $v;
367 last;
368 }
369 }
370 }
371 die "package '$pkgname' version '$param->{version}' is not avalable\n" if !$ver;
372 } else {
373 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
374 }
375
376 my $info = $pkgrecords->lookup($pkgname);
377
03d19bdc
DM
378 my $pkgfile = &$get_pkgfile($ver);
379 my $url;
380
381 die "changelog for '${pkgname}_$ver->{VerStr}' not available\n"
dab54677 382 if !($pkgfile && ($url = &$get_changelog_url($pkgname, $info, $ver->{VerStr}, $pkgfile->{Origin}, $pkgfile->{Component})));
6916396a
DM
383
384 my $data = "";
385
59c8caa9
DM
386 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
387 my $proxy = $dccfg->{http_proxy};
388
389 my $ua = LWP::UserAgent->new;
390 $ua->agent("PVE/1.0");
391 $ua->timeout(10);
392 $ua->max_size(1024*1024);
dab54677
DM
393 $ua->ssl_opts(verify_hostname => 0); # don't care for changelogs
394
59c8caa9 395 if ($proxy) {
dab54677 396 $ua->proxy(['http', 'https'], $proxy);
59c8caa9
DM
397 } else {
398 $ua->env_proxy;
399 }
400
dab54677
DM
401 my $username;
402 my $pw;
403
404 if ($pkgfile->{Origin} eq 'Proxmox' && $pkgfile->{Component} eq 'pve-enterprise') {
405 my $info = PVE::INotify::read_file('subscription');
406 if ($info->{status} eq 'Active') {
407 $username = $info->{key};
408 $pw = PVE::API2Tools::get_hwaddress();
409 $ua->credentials("enterprise.proxmox.com:443", 'pve-enterprise-repository',
410 $username, $pw);
411 }
412 }
413
59c8caa9 414 my $response = $ua->get($url);
6916396a 415
59c8caa9
DM
416 if ($response->is_success) {
417 $data = $response->decoded_content;
418 } else {
d3ce3535 419 PVE::Exception::raise($response->message, code => $response->code);
59c8caa9 420 }
6916396a
DM
421
422 return $data;
423 }});
424
8c1d3a4e
DM
425__PACKAGE__->register_method({
426 name => 'versions',
427 path => 'versions',
428 method => 'GET',
429 description => "Get package information for important Proxmox packages.",
430 permissions => {
431 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
432 },
433 parameters => {
434 additionalProperties => 0,
435 properties => {
436 node => get_standard_option('pve-node'),
437 },
438 },
439 returns => {
440 type => "array",
441 items => {
442 type => "object",
443 properties => {},
444 },
445 },
446 code => sub {
447 my ($param) = @_;
448
449 my $pkgname = $param->{name};
450
451 my $cache = &$get_apt_cache();
452 my $policy = $cache->policy;
453 my $pkgrecords = $cache->packages();
454
455 # try to use a resonable ordering (most important things first)
456 my @list = qw(proxmox-ve-2.6.32 pve-manager);
457
458 foreach my $pkgname (keys %$cache) {
459 if ($pkgname =~ m/pve-kernel-/) {
460 my $p = $cache->{$pkgname};
461 push @list, $pkgname if $p && $p->{CurrentState} eq 'Installed';
462 }
463 }
464
7232593a 465 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);
8c1d3a4e
DM
466
467 my $pkglist = [];
468
469 my (undef, undef, $kernel_release) = POSIX::uname();
470 my $pvever = PVE::pvecfg::version_text();
471
472 foreach my $pkgname (@list) {
473 my $p = $cache->{$pkgname};
474 my $info = $pkgrecords->lookup($pkgname);
475 my $candidate_ver = $policy->candidate($p);
476 my $res;
477 if (my $current_ver = $p->{CurrentVer}) {
478 $res = &$assemble_pkginfo($pkgname, $info, $current_ver,
479 $candidate_ver || $current_ver);
480 } elsif ($candidate_ver) {
481 $res = &$assemble_pkginfo($pkgname, $info, $candidate_ver,
482 $candidate_ver);
483 delete $res->{OldVersion};
484 } else {
485 next;
486 }
487 $res->{CurrentState} = $p->{CurrentState};
488
489 # hack: add some useful information (used by 'pveversion -v')
490 if ($pkgname eq 'pve-manager') {
491 $res->{ManagerVersion} = $pvever;
492 } elsif ($pkgname eq 'proxmox-ve-2.6.32') {
493 $res->{RunningKernel} = $kernel_release;
494 }
495
496 push @$pkglist, $res;
497 }
498
499 return $pkglist;
500 }});
501
8863ae95 5021;