]>
Commit | Line | Data |
---|---|---|
21299915 DM |
1 | package PVE::API2::APT; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
cd0bc36b | 5 | use File::stat (); |
21299915 | 6 | |
f5ed75de DM |
7 | use LWP::UserAgent; |
8 | ||
21299915 | 9 | use PVE::Tools qw(extract_param); |
f5ed75de | 10 | use PVE::Cluster; |
21299915 DM |
11 | use PVE::SafeSyslog; |
12 | use PVE::INotify; | |
396c9e4a | 13 | use PVE::Exception; |
21299915 DM |
14 | use PVE::RESTHandler; |
15 | use PVE::RPCEnvironment; | |
16 | ||
cd0bc36b | 17 | use JSON; |
21299915 DM |
18 | use PVE::JSONSchema qw(get_standard_option); |
19 | ||
20 | use AptPkg::Cache; | |
21 | use AptPkg::Version; | |
22 | use AptPkg::PkgRecords; | |
23 | ||
21299915 DM |
24 | my $get_apt_cache = sub { |
25 | ||
c06e9cc8 | 26 | my $apt_cache = AptPkg::Cache->new() || die "unable to initialize AptPkg::Cache\n"; |
21299915 DM |
27 | |
28 | return $apt_cache; | |
29 | }; | |
30 | ||
31 | use base qw(PVE::RESTHandler); | |
32 | ||
33 | __PACKAGE__->register_method({ | |
34 | name => 'index', | |
35 | path => '', | |
36 | method => 'GET', | |
37 | description => "Directory index for apt (Advanced Package Tool).", | |
38 | permissions => { | |
39 | user => 'all', | |
40 | }, | |
41 | parameters => { | |
42 | additionalProperties => 0, | |
43 | properties => { | |
44 | node => get_standard_option('pve-node'), | |
45 | }, | |
46 | }, | |
47 | returns => { | |
48 | type => "array", | |
49 | items => { | |
50 | type => "object", | |
51 | properties => { | |
52 | id => { type => 'string' }, | |
53 | }, | |
54 | }, | |
55 | links => [ { rel => 'child', href => "{id}" } ], | |
56 | }, | |
57 | code => sub { | |
58 | my ($param) = @_; | |
59 | ||
60 | my $res = [ | |
61 | { id => 'update' }, | |
62 | { id => 'upgrade' }, | |
63 | { id => 'changelog' }, | |
64 | ]; | |
65 | ||
66 | return $res; | |
67 | }}); | |
68 | ||
b688d438 DM |
69 | my $get_changelug_url = sub { |
70 | my ($pkgname, $info, $candidate_ver) = @_; | |
21299915 | 71 | |
00d48356 DM |
72 | my $changelog_url; |
73 | foreach my $verfile (@{$candidate_ver->{FileList}}) { | |
74 | my $pkgfile = $verfile->{File}; | |
75 | my $origin = $pkgfile->{Origin}; | |
76 | my $comp = $pkgfile->{Component}; | |
77 | if ($origin && $comp) { | |
78 | my $pkgver = $candidate_ver->{VerStr}; | |
371dcc92 DM |
79 | $pkgver =~ s/^\d+://; # strip epoch |
80 | my $srcpkg = $info->{SourcePkg} || $pkgname; | |
81 | my $firstLetter = substr($srcpkg, 0, 1); | |
00d48356 | 82 | if ($origin eq 'Debian') { |
b688d438 | 83 | $changelog_url = "http://packages.debian.org/changelogs/pool/$comp/" . |
371dcc92 | 84 | "$firstLetter/$srcpkg/${srcpkg}_$pkgver/changelog"; |
00d48356 DM |
85 | } |
86 | last; | |
87 | } | |
88 | } | |
b688d438 DM |
89 | return $changelog_url; |
90 | }; | |
91 | ||
92 | my $assemble_pkginfo = sub { | |
93 | my ($pkgname, $info, $current_ver, $candidate_ver) = @_; | |
00d48356 | 94 | |
21299915 DM |
95 | my $data = { |
96 | Package => $info->{Name}, | |
97 | Title => $info->{ShortDesc}, | |
98 | }; | |
99 | ||
b688d438 DM |
100 | if (my $changelog_url = &$get_changelug_url($pkgname, $info, $candidate_ver)) { |
101 | $data->{ChangeLogUrl} = $changelog_url; | |
102 | } | |
00d48356 | 103 | |
21299915 DM |
104 | if (my $desc = $info->{LongDesc}) { |
105 | $desc =~ s/^.*\n\s?//; # remove first line | |
106 | $desc =~ s/\n / /g; | |
107 | $data->{Description} = $desc; | |
108 | } | |
109 | ||
110 | foreach my $k (qw(Section Arch Priority)) { | |
111 | $data->{$k} = $candidate_ver->{$k}; | |
112 | } | |
113 | ||
114 | $data->{Version} = $candidate_ver->{VerStr}; | |
115 | $data->{OldVersion} = $current_ver->{VerStr}; | |
116 | ||
117 | return $data; | |
118 | }; | |
119 | ||
4806bc69 DM |
120 | # we try to cache results |
121 | my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates"; | |
122 | ||
123 | my $update_pve_pkgstatus = sub { | |
124 | ||
446f9217 DM |
125 | syslog('info', "update new package list: $pve_pkgstatus_fn"); |
126 | ||
4806bc69 DM |
127 | my $pkglist = []; |
128 | ||
129 | my $cache = &$get_apt_cache(); | |
130 | my $policy = $cache->policy; | |
131 | my $pkgrecords = $cache->packages(); | |
132 | ||
133 | foreach my $pkgname (keys %$cache) { | |
134 | my $p = $cache->{$pkgname}; | |
135 | next if $p->{SelectedState} ne 'Install'; | |
136 | my $current_ver = $p->{CurrentVer}; | |
137 | my $candidate_ver = $policy->candidate($p); | |
138 | ||
396c9e4a | 139 | if ($current_ver->{VerStr} ne $candidate_ver->{VerStr}) { |
4806bc69 DM |
140 | my $info = $pkgrecords->lookup($pkgname); |
141 | my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver); | |
142 | push @$pkglist, $res; | |
143 | } | |
144 | } | |
145 | ||
146 | PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist)); | |
147 | ||
148 | return $pkglist; | |
149 | }; | |
150 | ||
21299915 DM |
151 | __PACKAGE__->register_method({ |
152 | name => 'list_updates', | |
153 | path => 'update', | |
154 | method => 'GET', | |
155 | description => "List available updates.", | |
156 | permissions => { | |
157 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], | |
158 | }, | |
159 | protected => 1, | |
160 | proxyto => 'node', | |
161 | parameters => { | |
162 | additionalProperties => 0, | |
163 | properties => { | |
164 | node => get_standard_option('pve-node'), | |
165 | }, | |
166 | }, | |
167 | returns => { | |
168 | type => "array", | |
169 | items => { | |
170 | type => "object", | |
171 | properties => {}, | |
172 | }, | |
173 | }, | |
174 | code => sub { | |
175 | my ($param) = @_; | |
176 | ||
cd0bc36b DM |
177 | if (my $st1 = File::stat::stat($pve_pkgstatus_fn)) { |
178 | my $st2 = File::stat::stat("/var/cache/apt/pkgcache.bin"); | |
179 | my $st3 = File::stat::stat("/var/lib/dpkg/status"); | |
180 | ||
446f9217 | 181 | if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) { |
cd0bc36b DM |
182 | my $data; |
183 | eval { | |
184 | my $jsonstr = PVE::Tools::file_get_contents($pve_pkgstatus_fn, 5*1024*1024); | |
185 | $data = decode_json($jsonstr); | |
186 | }; | |
187 | if (my $err = $@) { | |
188 | warn "error readin cached package status in $pve_pkgstatus_fn\n"; | |
189 | # continue and overwrite cache with new content | |
190 | } else { | |
191 | return $data; | |
192 | } | |
193 | } | |
194 | } | |
195 | ||
4806bc69 DM |
196 | my $pkglist = &$update_pve_pkgstatus(); |
197 | ||
198 | return $pkglist; | |
199 | }}); | |
200 | ||
201 | __PACKAGE__->register_method({ | |
202 | name => 'update_database', | |
203 | path => 'update', | |
204 | method => 'POST', | |
205 | description => "This is used to resynchronize the package index files from their sources (apt-get update).", | |
206 | permissions => { | |
207 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], | |
208 | }, | |
209 | protected => 1, | |
210 | proxyto => 'node', | |
211 | parameters => { | |
212 | additionalProperties => 0, | |
213 | properties => { | |
214 | node => get_standard_option('pve-node'), | |
215 | }, | |
216 | }, | |
217 | returns => { | |
218 | type => 'string', | |
219 | }, | |
220 | code => sub { | |
221 | my ($param) = @_; | |
222 | ||
223 | my $rpcenv = PVE::RPCEnvironment::get(); | |
21299915 | 224 | |
4806bc69 | 225 | my $authuser = $rpcenv->get_user(); |
21299915 | 226 | |
4806bc69 DM |
227 | my $realcmd = sub { |
228 | my $upid = shift; | |
21299915 | 229 | |
4806bc69 | 230 | my $cmd = ['apt-get', 'update']; |
21299915 | 231 | |
4806bc69 DM |
232 | print "starting apt-get update\n"; |
233 | ||
234 | PVE::Tools::run_command($cmd); | |
235 | ||
236 | &$update_pve_pkgstatus(); | |
237 | ||
238 | return; | |
239 | }; | |
240 | ||
c2d3fbe0 DM |
241 | return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd); |
242 | ||
243 | }}); | |
244 | ||
245 | __PACKAGE__->register_method({ | |
246 | name => 'upgrade', | |
247 | path => 'upgrade', | |
248 | method => 'POST', | |
249 | description => "Install the newest versions of all packages (apt-get dist-upgrade).", | |
250 | permissions => { | |
251 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], | |
252 | }, | |
253 | protected => 1, | |
254 | proxyto => 'node', | |
255 | parameters => { | |
256 | additionalProperties => 0, | |
257 | properties => { | |
258 | node => get_standard_option('pve-node'), | |
259 | }, | |
260 | }, | |
261 | returns => { | |
262 | type => 'string', | |
263 | }, | |
264 | code => sub { | |
265 | my ($param) = @_; | |
266 | ||
267 | my $rpcenv = PVE::RPCEnvironment::get(); | |
268 | ||
269 | my $authuser = $rpcenv->get_user(); | |
270 | ||
271 | my $realcmd = sub { | |
272 | my $upid = shift; | |
273 | ||
274 | my $cmd = ['apt-get', 'dist-upgrade', '--assume-yes']; | |
275 | ||
9f6658a9 DM |
276 | push @$cmd, '-o', 'Dpkg::Options::=--force-confdef'; |
277 | ||
278 | push @$cmd, '-o', 'Dpkg::Options::=--force-confold'; | |
279 | ||
c2d3fbe0 DM |
280 | print "starting apt-get dist-upgrade\n"; |
281 | ||
282 | $ENV{DEBIAN_FRONTEND} = 'noninteractive'; | |
283 | ||
284 | PVE::Tools::run_command($cmd); | |
285 | ||
286 | &$update_pve_pkgstatus(); | |
287 | ||
288 | return; | |
289 | }; | |
290 | ||
291 | return $rpcenv->fork_worker('aptupgrade', undef, $authuser, $realcmd); | |
cd0bc36b | 292 | |
21299915 DM |
293 | }}); |
294 | ||
b688d438 DM |
295 | __PACKAGE__->register_method({ |
296 | name => 'changelog', | |
297 | path => 'changelog', | |
298 | method => 'GET', | |
299 | description => "Get package changelogs.", | |
300 | permissions => { | |
301 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], | |
302 | }, | |
303 | parameters => { | |
304 | additionalProperties => 0, | |
305 | properties => { | |
306 | node => get_standard_option('pve-node'), | |
307 | name => { | |
308 | description => "Package name.", | |
309 | type => 'string', | |
310 | }, | |
311 | version => { | |
312 | description => "Package version.", | |
313 | type => 'string', | |
314 | optional => 1, | |
315 | }, | |
316 | }, | |
317 | }, | |
318 | returns => { | |
319 | type => "string", | |
320 | }, | |
321 | code => sub { | |
322 | my ($param) = @_; | |
323 | ||
324 | my $pkgname = $param->{name}; | |
325 | ||
326 | my $cache = &$get_apt_cache(); | |
327 | my $policy = $cache->policy; | |
328 | my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n"; | |
329 | my $pkgrecords = $cache->packages(); | |
330 | ||
331 | my $ver; | |
332 | if ($param->{version}) { | |
333 | if (my $available = $p->{VersionList}) { | |
334 | for my $v (@$available) { | |
335 | if ($v->{VerStr} eq $param->{version}) { | |
336 | $ver = $v; | |
337 | last; | |
338 | } | |
339 | } | |
340 | } | |
341 | die "package '$pkgname' version '$param->{version}' is not avalable\n" if !$ver; | |
342 | } else { | |
343 | $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n"; | |
344 | } | |
345 | ||
346 | my $info = $pkgrecords->lookup($pkgname); | |
347 | ||
348 | my $url = &$get_changelug_url($pkgname, $info, $ver) || | |
349 | die "changelog for '${pkgname}_$ver->{VerStr}' not available\n"; | |
350 | ||
351 | my $data = ""; | |
352 | ||
f5ed75de DM |
353 | my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); |
354 | my $proxy = $dccfg->{http_proxy}; | |
355 | ||
356 | my $ua = LWP::UserAgent->new; | |
357 | $ua->agent("PVE/1.0"); | |
358 | $ua->timeout(10); | |
359 | $ua->max_size(1024*1024); | |
360 | ||
361 | if ($proxy) { | |
362 | $ua->proxy(['http'], $proxy); | |
363 | } else { | |
364 | $ua->env_proxy; | |
365 | } | |
366 | ||
367 | my $response = $ua->get($url); | |
b688d438 | 368 | |
f5ed75de DM |
369 | if ($response->is_success) { |
370 | $data = $response->decoded_content; | |
371 | } else { | |
396c9e4a | 372 | PVE::Exception::raise($response->message, code => $response->code); |
f5ed75de | 373 | } |
b688d438 DM |
374 | |
375 | return $data; | |
376 | }}); | |
377 | ||
21299915 | 378 | 1; |