1 use apt_pkg_native
::Cache
;
2 use anyhow
::{Error, bail}
;
3 use serde_json
::{json, Value}
;
5 use proxmox
::{list_subdirs_api_method, const_regex}
;
6 use proxmox
::api
::{api, Router, Permission, SubdirMap}
;
8 use crate::config
::acl
::PRIV_SYS_AUDIT
;
9 use crate::api2
::types
::{APTUpdateInfo, NODE_SCHEMA}
;
12 VERSION_EPOCH_REGEX
= r
"^\d+:";
13 FILENAME_EXTRACT_REGEX
= r
"^.*/.*?_(.*)_Packages$";
16 // FIXME: Replace with call to 'apt changelog <pkg> --print-uris'. Currently
17 // not possible as our packages do not have a URI set in their Release file
26 ) -> Result
<String
, Error
> {
28 bail
!("no origin available for package {}", package
);
31 if origin
== "Debian" {
32 let source_version
= (VERSION_EPOCH_REGEX
.regex_obj
)().replace_all(source_version
, "");
34 let prefix
= if source_pkg
.starts_with("lib") {
40 let prefix
= match prefix
{
42 None
=> bail
!("cannot get starting characters of package name '{}'", package
)
45 // note: security updates seem to not always upload a changelog for
46 // their package version, so this only works *most* of the time
47 return Ok(format
!("https://metadata.ftp-master.debian.org/changelogs/main/{}/{}/{}_{}_changelog",
48 prefix
, source_pkg
, source_pkg
, source_version
));
50 } else if origin
== "Proxmox" {
51 let version
= (VERSION_EPOCH_REGEX
.regex_obj
)().replace_all(version
, "");
53 let base
= match (FILENAME_EXTRACT_REGEX
.regex_obj
)().captures(filename
) {
55 let base_capture
= captures
.get(1);
57 Some(base_underscore
) => base_underscore
.as_str().replace("_", "/"),
58 None
=> bail
!("incompatible filename, cannot find regex group")
61 None
=> bail
!("incompatible filename, doesn't match regex")
64 return Ok(format
!("http://download.proxmox.com/{}/{}_{}.changelog",
65 base
, package
, version
));
68 bail
!("unknown origin ({}) or component ({})", origin
, component
)
71 fn list_installed_apt_packages
<F
: Fn(&str, &str, &str) -> bool
>(filter
: F
)
72 -> Vec
<APTUpdateInfo
> {
74 let mut ret
= Vec
::new();
76 // note: this is not an 'apt update', it just re-reads the cache from disk
77 let mut cache
= Cache
::get_singleton();
80 let mut cache_iter
= cache
.iter();
83 let view
= match cache_iter
.next() {
88 let current_version
= match view
.current_version() {
92 let candidate_version
= match view
.candidate_version() {
94 // if there's no candidate (i.e. no update) get info of currently
95 // installed version instead
96 None
=> current_version
.clone()
99 let package
= view
.name();
100 if filter(&package
, ¤t_version
, &candidate_version
) {
101 let mut origin_res
= "unknown".to_owned();
102 let mut section_res
= "unknown".to_owned();
103 let mut priority_res
= "unknown".to_owned();
104 let mut change_log_url
= "".to_owned();
105 let mut short_desc
= package
.clone();
106 let mut long_desc
= "".to_owned();
108 // get additional information via nested APT 'iterators'
109 let mut view_iter
= view
.versions();
110 while let Some(ver
) = view_iter
.next() {
111 if ver
.version() == candidate_version
{
112 if let Some(section
) = ver
.section() {
113 section_res
= section
;
116 if let Some(prio
) = ver
.priority_type() {
120 // assume every package has only one origin file (not
121 // origin, but origin *file*, for some reason those seem to
122 // be different concepts in APT)
123 let mut origin_iter
= ver
.origin_iter();
124 let origin
= origin_iter
.next();
125 if let Some(origin
) = origin
{
127 if let Some(sd
) = origin
.short_desc() {
131 if let Some(ld
) = origin
.long_desc() {
135 // the package files appear in priority order, meaning
136 // the one for the candidate version is first
137 let mut pkg_iter
= origin
.file();
138 let pkg_file
= pkg_iter
.next();
139 if let Some(pkg_file
) = pkg_file
{
140 if let Some(origin_name
) = pkg_file
.origin() {
141 origin_res
= origin_name
;
144 let filename
= pkg_file
.file_name();
145 let source_pkg
= ver
.source_package();
146 let source_ver
= ver
.source_version();
147 let component
= pkg_file
.component();
149 // build changelog URL from gathered information
150 // ignore errors, use empty changelog instead
151 let url
= get_changelog_url(&package
, &filename
, &source_pkg
,
152 &candidate_version
, &source_ver
, &origin_res
, &component
);
153 if let Ok(url
) = url
{
154 change_log_url
= url
;
163 let info
= APTUpdateInfo
{
167 description
: long_desc
,
170 version
: candidate_version
,
171 old_version
: current_version
,
172 priority
: priority_res
,
173 section
: section_res
,
191 description
: "A list of packages with available updates.",
193 items
: { type: APTUpdateInfo }
,
196 permission
: &Permission
::Privilege(&[], PRIV_SYS_AUDIT
, false),
199 /// List available APT updates
200 fn apt_update_available(_param
: Value
) -> Result
<Value
, Error
> {
201 let ret
= list_installed_apt_packages(|_pkg
, cur_ver
, can_ver
| cur_ver
!= can_ver
);
205 const SUBDIRS
: SubdirMap
= &[
206 ("update", &Router
::new().get(&API_METHOD_APT_UPDATE_AVAILABLE
)),
209 pub const ROUTER
: Router
= Router
::new()
210 .get(&list_subdirs_api_method
!(SUBDIRS
))