]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/APT.pm
install CA certificates used by our repositories
[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 {
87 my ($pkgname, $info, $pkgver, $origin) = @_;
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/" .
96 "${srcpkg}_$pkgver/changelog";
00d48356
DM
97 }
98 }
a972b69d 99
b688d438
DM
100 return $changelog_url;
101};
102
103my $assemble_pkginfo = sub {
104 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
00d48356 105
21299915
DM
106 my $data = {
107 Package => $info->{Name},
108 Title => $info->{ShortDesc},
a972b69d 109 Origin => 'unknown',
21299915
DM
110 };
111
a972b69d
DM
112 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
113 my $origin = $pkgfile->{Origin};
114 $data->{Origin} = $origin;
115 if (my $changelog_url = &$get_changelog_url($pkgname, $info, $candidate_ver->{VerStr}, $origin)) {
116 $data->{ChangeLogUrl} = $changelog_url;
117 }
b688d438 118 }
00d48356 119
21299915
DM
120 if (my $desc = $info->{LongDesc}) {
121 $desc =~ s/^.*\n\s?//; # remove first line
122 $desc =~ s/\n / /g;
123 $data->{Description} = $desc;
124 }
125
126 foreach my $k (qw(Section Arch Priority)) {
127 $data->{$k} = $candidate_ver->{$k};
128 }
129
130 $data->{Version} = $candidate_ver->{VerStr};
131 $data->{OldVersion} = $current_ver->{VerStr};
132
133 return $data;
134};
135
4806bc69
DM
136# we try to cache results
137my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates";
138
139my $update_pve_pkgstatus = sub {
140
446f9217
DM
141 syslog('info', "update new package list: $pve_pkgstatus_fn");
142
4806bc69
DM
143 my $pkglist = [];
144
145 my $cache = &$get_apt_cache();
146 my $policy = $cache->policy;
147 my $pkgrecords = $cache->packages();
148
149 foreach my $pkgname (keys %$cache) {
150 my $p = $cache->{$pkgname};
151 next if $p->{SelectedState} ne 'Install';
152 my $current_ver = $p->{CurrentVer};
153 my $candidate_ver = $policy->candidate($p);
154
396c9e4a 155 if ($current_ver->{VerStr} ne $candidate_ver->{VerStr}) {
4806bc69
DM
156 my $info = $pkgrecords->lookup($pkgname);
157 my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver);
158 push @$pkglist, $res;
159 }
160 }
161
162 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
163
164 return $pkglist;
165};
166
21299915
DM
167__PACKAGE__->register_method({
168 name => 'list_updates',
169 path => 'update',
170 method => 'GET',
171 description => "List available updates.",
172 permissions => {
173 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
174 },
175 protected => 1,
176 proxyto => 'node',
177 parameters => {
178 additionalProperties => 0,
179 properties => {
180 node => get_standard_option('pve-node'),
181 },
182 },
183 returns => {
184 type => "array",
185 items => {
186 type => "object",
187 properties => {},
188 },
189 },
190 code => sub {
191 my ($param) = @_;
192
cd0bc36b
DM
193 if (my $st1 = File::stat::stat($pve_pkgstatus_fn)) {
194 my $st2 = File::stat::stat("/var/cache/apt/pkgcache.bin");
195 my $st3 = File::stat::stat("/var/lib/dpkg/status");
196
446f9217 197 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
cd0bc36b
DM
198 my $data;
199 eval {
200 my $jsonstr = PVE::Tools::file_get_contents($pve_pkgstatus_fn, 5*1024*1024);
201 $data = decode_json($jsonstr);
202 };
203 if (my $err = $@) {
204 warn "error readin cached package status in $pve_pkgstatus_fn\n";
205 # continue and overwrite cache with new content
206 } else {
207 return $data;
208 }
209 }
210 }
211
4806bc69
DM
212 my $pkglist = &$update_pve_pkgstatus();
213
214 return $pkglist;
215 }});
216
217__PACKAGE__->register_method({
218 name => 'update_database',
219 path => 'update',
220 method => 'POST',
221 description => "This is used to resynchronize the package index files from their sources (apt-get update).",
222 permissions => {
223 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
224 },
225 protected => 1,
226 proxyto => 'node',
227 parameters => {
228 additionalProperties => 0,
229 properties => {
230 node => get_standard_option('pve-node'),
a88002cf
DM
231 notify => {
232 type => 'boolean',
233 description => "Send notification mail about new packages (to email address specified for user 'root\@pam').",
234 optional => 1,
235 default => 0,
236 },
4806bc69
DM
237 },
238 },
239 returns => {
240 type => 'string',
241 },
242 code => sub {
243 my ($param) = @_;
244
245 my $rpcenv = PVE::RPCEnvironment::get();
21299915 246
4806bc69 247 my $authuser = $rpcenv->get_user();
21299915 248
4806bc69
DM
249 my $realcmd = sub {
250 my $upid = shift;
21299915 251
4806bc69 252 my $cmd = ['apt-get', 'update'];
21299915 253
4806bc69
DM
254 print "starting apt-get update\n";
255
256 PVE::Tools::run_command($cmd);
257
a88002cf
DM
258 my $pkglist = &$update_pve_pkgstatus();
259
260 if ($param->{notify} && scalar(@$pkglist)) {
261
262 my $usercfg = PVE::Cluster::cfs_read_file("user.cfg");
263 my $rootcfg = $usercfg->{users}->{'root@pam'} || {};
264 my $mailto = $rootcfg->{email};
265
266 if ($mailto) {
267 my $hostname = `hostname -f` || PVE::INotify::nodename();
268 chomp $hostname;
269
270 my $data = "Content-Type: text/plain;charset=\"UTF8\"\n";
271 $data .= "Content-Transfer-Encoding: 8bit\n";
272 $data .= "FROM: <root\@$hostname>\n";
273 $data .= "TO: $mailto\n";
274 $data .= "SUBJECT: New software packages available ($hostname)\n";
275 $data .= "\n";
276
277 $data .= "The following updates are available:\n\n";
278
279 foreach my $p (sort {$a->{Package} cmp $b->{Package} } @$pkglist) {
280 $data .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
281 }
282
283 my $fh = IO::File->new("|sendmail -B 8BITMIME $mailto") ||
284 die "unable to open 'sendmail' - $!";
285
286 print $fh $data;
287
288 $fh->close();
289 }
290 }
4806bc69
DM
291
292 return;
293 };
294
c2d3fbe0 295 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
cd0bc36b 296
21299915
DM
297 }});
298
b688d438
DM
299__PACKAGE__->register_method({
300 name => 'changelog',
301 path => 'changelog',
302 method => 'GET',
303 description => "Get package changelogs.",
304 permissions => {
305 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
306 },
307 parameters => {
308 additionalProperties => 0,
309 properties => {
310 node => get_standard_option('pve-node'),
311 name => {
312 description => "Package name.",
313 type => 'string',
314 },
315 version => {
316 description => "Package version.",
317 type => 'string',
318 optional => 1,
319 },
320 },
321 },
322 returns => {
323 type => "string",
324 },
325 code => sub {
326 my ($param) = @_;
327
328 my $pkgname = $param->{name};
329
330 my $cache = &$get_apt_cache();
331 my $policy = $cache->policy;
332 my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n";
333 my $pkgrecords = $cache->packages();
334
335 my $ver;
336 if ($param->{version}) {
337 if (my $available = $p->{VersionList}) {
338 for my $v (@$available) {
339 if ($v->{VerStr} eq $param->{version}) {
340 $ver = $v;
341 last;
342 }
343 }
344 }
345 die "package '$pkgname' version '$param->{version}' is not avalable\n" if !$ver;
346 } else {
347 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
348 }
349
350 my $info = $pkgrecords->lookup($pkgname);
351
a972b69d
DM
352 my $pkgfile = &$get_pkgfile($ver);
353 my $url;
354
355 die "changelog for '${pkgname}_$ver->{VerStr}' not available\n"
356 if !($pkgfile && ($url = &$get_changelog_url($pkgname, $info, $ver->{VerStr}, $pkgfile->{Origin})));
b688d438
DM
357
358 my $data = "";
359
f5ed75de
DM
360 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
361 my $proxy = $dccfg->{http_proxy};
362
363 my $ua = LWP::UserAgent->new;
364 $ua->agent("PVE/1.0");
365 $ua->timeout(10);
366 $ua->max_size(1024*1024);
367
368 if ($proxy) {
369 $ua->proxy(['http'], $proxy);
370 } else {
371 $ua->env_proxy;
372 }
373
374 my $response = $ua->get($url);
b688d438 375
f5ed75de
DM
376 if ($response->is_success) {
377 $data = $response->decoded_content;
378 } else {
396c9e4a 379 PVE::Exception::raise($response->message, code => $response->code);
f5ed75de 380 }
b688d438
DM
381
382 return $data;
383 }});
384
8794761f
DM
385__PACKAGE__->register_method({
386 name => 'versions',
387 path => 'versions',
388 method => 'GET',
389 description => "Get package information for important Proxmox packages.",
390 permissions => {
391 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
392 },
393 parameters => {
394 additionalProperties => 0,
395 properties => {
396 node => get_standard_option('pve-node'),
397 },
398 },
399 returns => {
400 type => "array",
401 items => {
402 type => "object",
403 properties => {},
404 },
405 },
406 code => sub {
407 my ($param) = @_;
408
409 my $pkgname = $param->{name};
410
411 my $cache = &$get_apt_cache();
412 my $policy = $cache->policy;
413 my $pkgrecords = $cache->packages();
414
415 # try to use a resonable ordering (most important things first)
416 my @list = qw(proxmox-ve-2.6.32 pve-manager);
417
418 foreach my $pkgname (keys %$cache) {
419 if ($pkgname =~ m/pve-kernel-/) {
420 my $p = $cache->{$pkgname};
421 push @list, $pkgname if $p && $p->{CurrentState} eq 'Installed';
422 }
423 }
424
026c392b 425 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);
8794761f
DM
426
427 my $pkglist = [];
428
429 my (undef, undef, $kernel_release) = POSIX::uname();
430 my $pvever = PVE::pvecfg::version_text();
431
432 foreach my $pkgname (@list) {
433 my $p = $cache->{$pkgname};
434 my $info = $pkgrecords->lookup($pkgname);
435 my $candidate_ver = $policy->candidate($p);
436 my $res;
437 if (my $current_ver = $p->{CurrentVer}) {
438 $res = &$assemble_pkginfo($pkgname, $info, $current_ver,
439 $candidate_ver || $current_ver);
440 } elsif ($candidate_ver) {
441 $res = &$assemble_pkginfo($pkgname, $info, $candidate_ver,
442 $candidate_ver);
443 delete $res->{OldVersion};
444 } else {
445 next;
446 }
447 $res->{CurrentState} = $p->{CurrentState};
448
449 # hack: add some useful information (used by 'pveversion -v')
450 if ($pkgname eq 'pve-manager') {
451 $res->{ManagerVersion} = $pvever;
452 } elsif ($pkgname eq 'proxmox-ve-2.6.32') {
453 $res->{RunningKernel} = $kernel_release;
454 }
455
456 push @$pkglist, $res;
457 }
458
459 return $pkglist;
460 }});
461
21299915 4621;