all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Fabian Ebner <f.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH v4 proxmox-apt 04/10] add check_repositories function
Date: Fri,  2 Apr 2021 13:20:45 +0200	[thread overview]
Message-ID: <20210402112051.14628-5-f.ebner@proxmox.com> (raw)
In-Reply-To: <20210402112051.14628-1-f.ebner@proxmox.com>

which checks for bad suites and official URIs.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
---

Changes from v3:
    * switched to taking APTRepositoryFile slice as input
    * don't use vec! for a constant array
    * replace APTRepositoryWarning with a more general APTRepositoryInfo
    * warn about 'testing' when checking suites
    * warn about 'bullseye', but have it be a special warning so the UI
      can avoid showing it while a major upgrade happens
    * check for official host names and return a 'badge' info
    * add/adapt tests

 src/repositories/check.rs                 | 151 +++++++++++++++++++++-
 src/repositories/mod.rs                   |  19 ++-
 src/types.rs                              |  19 +++
 tests/repositories.rs                     |  97 +++++++++++++-
 tests/sources.list.d.expected/bad.sources |  30 +++++
 tests/sources.list.d/bad.sources          |  29 +++++
 6 files changed, 341 insertions(+), 4 deletions(-)
 create mode 100644 tests/sources.list.d.expected/bad.sources
 create mode 100644 tests/sources.list.d/bad.sources

diff --git a/src/repositories/check.rs b/src/repositories/check.rs
index d0656cd..809b7bc 100644
--- a/src/repositories/check.rs
+++ b/src/repositories/check.rs
@@ -1,6 +1,22 @@
 use anyhow::{bail, Error};
 
-use crate::types::{APTRepository, APTRepositoryFileType};
+use crate::types::{
+    APTRepository, APTRepositoryFile, APTRepositoryFileType, APTRepositoryInfo,
+    APTRepositoryPackageType,
+};
+
+/// Checks if `suite` is some variant of `base_suite`, e.g. `buster-backports`
+/// is a variant of `buster`.
+fn suite_is_variant(suite: &str, base_suite: &str) -> bool {
+    matches!(
+        suite.strip_prefix(base_suite),
+        Some("")
+            | Some("-backports")
+            | Some("-backports-sloppy")
+            | Some("-updates")
+            | Some("/updates")
+    )
+}
 
 impl APTRepository {
     /// Makes sure that all basic properties of a repository are present and
@@ -86,4 +102,137 @@ impl APTRepository {
             false
         }
     }
+
+    /// Checks if old or unstable suites are configured and also that the
+    /// `stable` keyword is not used.
+    fn check_suites(&self, add_info: &mut dyn FnMut(String, String)) {
+        let old_suites = [
+            "lenny",
+            "squeeze",
+            "wheezy",
+            "jessie",
+            "stretch",
+            "oldoldstable",
+            "oldstable",
+        ];
+
+        let next_suite = "bullseye";
+
+        let new_suites = ["testing", "unstable", "sid", "experimental"];
+
+        if self
+            .types
+            .iter()
+            .any(|package_type| *package_type == APTRepositoryPackageType::Deb)
+        {
+            for suite in self.suites.iter() {
+                if old_suites
+                    .iter()
+                    .any(|base_suite| suite_is_variant(suite, base_suite))
+                {
+                    add_info(
+                        "warning".to_string(),
+                        format!("old suite '{}' configured!", suite),
+                    );
+                }
+
+                if suite_is_variant(suite, next_suite) {
+                    add_info(
+                        "ignore-pre-upgrade-warning".to_string(),
+                        format!("suite '{}' should not be used in production!", suite),
+                    );
+                }
+
+                if new_suites
+                    .iter()
+                    .any(|base_suite| suite_is_variant(suite, base_suite))
+                {
+                    add_info(
+                        "warning".to_string(),
+                        format!("suite '{}' should not be used in production!", suite),
+                    );
+                }
+
+                if suite_is_variant(suite, "stable") {
+                    add_info(
+                        "warning".to_string(),
+                        "use the name of the stable distribution instead of 'stable'!".to_string(),
+                    );
+                }
+            }
+        }
+    }
+
+    /// Checks if an official host is configured in the repository.
+    fn check_uris(&self) -> Option<(String, String)> {
+        let official_host = |domains: &Vec<&str>| {
+            match domains.len() {
+                3 => match domains[0] {
+                    "ftp" | "deb" | "security" => domains[1] == "debian" && domains[2] == "org",
+                    "download" | "enterprise" => domains[1] == "proxmox" && domains[2] == "com",
+                    _ => false,
+                },
+                // ftp.*.debian.org
+                4 => domains[0] == "ftp" && domains[2] == "debian" && domains[3] == "org",
+                _ => false,
+            }
+        };
+
+        for uri in self.uris.iter() {
+            if let Some(begin) = uri.find("://") {
+                let mut host = uri.split_at(begin + 3).1;
+                if let Some(end) = host.find('/') {
+                    host = host.split_at(end).0;
+                } // otherwise assume everything belongs is the host
+
+                let domains = host.split('.').collect();
+
+                if official_host(&domains) {
+                    return Some(("badge".to_string(), "official host name".to_string()));
+                }
+            }
+        }
+
+        None
+    }
+}
+
+impl APTRepositoryFile {
+    /// Checks if old or unstable suites are configured and also that the
+    /// `stable` keyword is not used.
+    pub fn check_suites(&self) -> Vec<APTRepositoryInfo> {
+        let mut infos = vec![];
+
+        for (n, repo) in self.repositories.iter().enumerate() {
+            let mut add_info = |kind, message| {
+                infos.push(APTRepositoryInfo {
+                    path: self.path.clone(),
+                    number: n + 1,
+                    kind,
+                    message,
+                })
+            };
+            repo.check_suites(&mut add_info);
+        }
+
+        infos
+    }
+
+    /// Checks for official URIs.
+    pub fn check_uris(&self) -> Vec<APTRepositoryInfo> {
+        let mut infos = vec![];
+
+        for (n, repo) in self.repositories.iter().enumerate() {
+            if let Some((kind, message)) = repo.check_uris() {
+                infos.push(APTRepositoryInfo {
+                    path: self.path.clone(),
+                    number: n + 1,
+                    kind,
+                    message,
+                });
+            }
+        }
+
+        infos
+    }
 }
diff --git a/src/repositories/mod.rs b/src/repositories/mod.rs
index b7919a9..c2bbc06 100644
--- a/src/repositories/mod.rs
+++ b/src/repositories/mod.rs
@@ -4,7 +4,7 @@ use anyhow::{bail, format_err, Error};
 
 use crate::types::{
     APTRepository, APTRepositoryFile, APTRepositoryFileError, APTRepositoryFileType,
-    APTRepositoryOption,
+    APTRepositoryInfo, APTRepositoryOption,
 };
 
 mod list_parser;
@@ -148,6 +148,23 @@ impl APTRepositoryFile {
     }
 }
 
+/// Provides additional information about the repositories.
+///
+/// The kind of information can be:
+/// `warnings` for bad suites.
+/// `ignore-pre-upgrade-warning` when the next stable suite is configured.
+/// `badge` for official URIs.
+pub fn check_repositories(files: &[APTRepositoryFile]) -> Vec<APTRepositoryInfo> {
+    let mut infos = vec![];
+
+    for file in files.iter() {
+        infos.append(&mut file.check_suites());
+        infos.append(&mut file.check_uris());
+    }
+
+    infos
+}
+
 /// Checks if the enterprise repository for the specified Proxmox product is
 /// configured and enabled.
 pub fn enterprise_repository_enabled(files: &[APTRepositoryFile], product: &str) -> bool {
diff --git a/src/types.rs b/src/types.rs
index 45b8455..94b6411 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -244,3 +244,22 @@ impl std::error::Error for APTRepositoryFileError {
         None
     }
 }
+
+#[api]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+/// Additional information for a repository.
+pub struct APTRepositoryInfo {
+    /// Path to the defining file.
+    #[serde(skip_serializing_if = "String::is_empty")]
+    pub path: String,
+
+    /// Number of the associated respository within the file.
+    pub number: usize,
+
+    /// Info kind (e.g. "warning")
+    pub kind: String,
+
+    /// Info message
+    pub message: String,
+}
diff --git a/tests/repositories.rs b/tests/repositories.rs
index d23beac..2b9b208 100644
--- a/tests/repositories.rs
+++ b/tests/repositories.rs
@@ -3,9 +3,10 @@ use std::path::PathBuf;
 use anyhow::{bail, format_err, Error};
 
 use proxmox_apt::repositories::{
-    enterprise_repository_enabled, no_subscription_repository_enabled, write_repositories,
+    check_repositories, enterprise_repository_enabled, no_subscription_repository_enabled,
+    write_repositories,
 };
-use proxmox_apt::types::APTRepositoryFile;
+use proxmox_apt::types::{APTRepositoryFile, APTRepositoryInfo};
 
 #[test]
 fn test_parse_write() -> Result<(), Error> {
@@ -161,3 +162,95 @@ fn test_proxmox_repositories() -> Result<(), Error> {
 
     Ok(())
 }
+
+#[test]
+fn test_check_repositories() -> Result<(), Error> {
+    let test_dir = std::env::current_dir()?.join("tests");
+    let read_dir = test_dir.join("sources.list.d");
+
+    let absolute_suite_list = read_dir.join("absolute_suite.list");
+    let mut file = APTRepositoryFile::new(&absolute_suite_list)?.unwrap();
+    file.parse()?;
+
+    let infos = check_repositories(&vec![file]);
+
+    assert_eq!(infos.is_empty(), true);
+    let pve_list = read_dir.join("pve.list");
+    let mut file = APTRepositoryFile::new(&pve_list)?.unwrap();
+    file.parse()?;
+
+    let path_string = pve_list.into_os_string().into_string().unwrap();
+
+    let mut expected_infos = vec![];
+    for n in 1..=5 {
+        expected_infos.push(APTRepositoryInfo {
+            path: path_string.clone(),
+            number: n,
+            kind: "badge".to_string(),
+            message: "official host name".to_string(),
+        });
+    }
+
+    let mut infos = check_repositories(&vec![file]);
+
+    assert_eq!(infos.sort(), expected_infos.sort());
+
+    let bad_sources = read_dir.join("bad.sources");
+    let mut file = APTRepositoryFile::new(&bad_sources)?.unwrap();
+    file.parse()?;
+
+    let path_string = bad_sources.into_os_string().into_string().unwrap();
+
+    let mut expected_infos = vec![
+        APTRepositoryInfo {
+            path: path_string.clone(),
+            number: 1,
+            kind: "warning".to_string(),
+            message: "suite 'sid' should not be used in production!".to_string(),
+        },
+        APTRepositoryInfo {
+            path: path_string.clone(),
+            number: 2,
+            kind: "warning".to_string(),
+            message: "old suite 'lenny-backports' configured!".to_string(),
+        },
+        APTRepositoryInfo {
+            path: path_string.clone(),
+            number: 3,
+            kind: "warning".to_string(),
+            message: "old suite 'stretch/updates' configured!".to_string(),
+        },
+        APTRepositoryInfo {
+            path: path_string.clone(),
+            number: 4,
+            kind: "warning".to_string(),
+            message: "use the name of the stable distribution instead of 'stable'!".to_string(),
+        },
+        APTRepositoryInfo {
+            path: path_string.clone(),
+            number: 5,
+            kind: "ignore-pre-upgrade-warning".to_string(),
+            message: "suite 'bullseye' should not be used in production!".to_string(),
+        },
+        APTRepositoryInfo {
+            path: path_string.clone(),
+            number: 6,
+            kind: "warning".to_string(),
+            message: "suite 'testing' should not be used in production!".to_string(),
+        },
+    ];
+    for n in 1..=6 {
+        expected_infos.push(APTRepositoryInfo {
+            path: path_string.clone(),
+            number: n,
+            kind: "badge".to_string(),
+            message: "official URI".to_string(),
+        });
+    }
+
+    let mut infos = check_repositories(&vec![file]);
+
+    assert_eq!(infos.sort(), expected_infos.sort());
+
+    Ok(())
+}
diff --git a/tests/sources.list.d.expected/bad.sources b/tests/sources.list.d.expected/bad.sources
new file mode 100644
index 0000000..36ff7a0
--- /dev/null
+++ b/tests/sources.list.d.expected/bad.sources
@@ -0,0 +1,30 @@
+Types: deb
+URIs: http://ftp.at.debian.org/debian
+Suites: sid
+Components: main contrib
+
+Types: deb
+URIs: http://ftp.at.debian.org/debian
+Suites: lenny-backports
+Components: contrib
+
+Types: deb
+URIs: http://security.debian.org
+Suites: stretch/updates
+Components: main contrib
+
+Types: deb
+URIs: http://ftp.at.debian.org/debian
+Suites: stable
+Components: main
+
+Types: deb
+URIs: http://ftp.at.debian.org/debian
+Suites: bullseye
+Components: main
+
+Types: deb
+URIs: http://ftp.at.debian.org/debian
+Suites: testing
+Components: main
+
diff --git a/tests/sources.list.d/bad.sources b/tests/sources.list.d/bad.sources
new file mode 100644
index 0000000..6f2524a
--- /dev/null
+++ b/tests/sources.list.d/bad.sources
@@ -0,0 +1,29 @@
+Types: deb
+URIs: http://ftp.at.debian.org/debian
+Suites: sid
+Components: main contrib
+
+Types: deb
+URIs: http://ftp.at.debian.org/debian
+Suites: lenny-backports
+Components: contrib
+
+Types: deb
+URIs: http://security.debian.org
+Suites: stretch/updates
+Components: main contrib
+
+Suites: stable
+URIs: http://ftp.at.debian.org/debian
+Components: main
+Types: deb
+
+Suites: bullseye
+URIs: http://ftp.at.debian.org/debian
+Components: main
+Types: deb
+
+Suites: testing
+URIs: http://ftp.at.debian.org/debian
+Components: main
+Types: deb
-- 
2.20.1





  parent reply	other threads:[~2021-04-02 11:21 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-04-02 11:20 [pbs-devel] [PATCH-SERIES v4] APT repositories API/UI Fabian Ebner
2021-04-02 11:20 ` [pbs-devel] [PATCH v4 proxmox-apt 01/10] initial commit Fabian Ebner
2021-04-02 11:20 ` [pbs-devel] [PATCH v4 proxmox-apt 02/10] add files for Debian packaging Fabian Ebner
2021-04-02 11:20 ` [pbs-devel] [PATCH v4 proxmox-apt 03/10] add functions to check for Proxmox repositories Fabian Ebner
2021-04-02 11:20 ` Fabian Ebner [this message]
2021-04-02 11:20 ` [pbs-devel] [PATCH v4 proxmox-backup 05/10] depend on new proxmox-apt crate Fabian Ebner
2021-04-02 11:20 ` [pbs-devel] [PATCH v4 proxmox-backup 06/10] api: apt: add repositories call Fabian Ebner
2021-04-02 11:20 ` [pbs-devel] [PATCH v4 proxmox-backup 07/10] ui: add panel for APT repositories Fabian Ebner
2021-04-02 11:20 ` [pbs-devel] [PATCH v4 proxmox-backup 08/10] api: apt: add check_repositories_call Fabian Ebner
2021-04-02 11:20 ` [pbs-devel] [PATCH v4 widget-toolkit 09/10] add UI for APT repositories Fabian Ebner
2021-04-02 11:20 ` [pbs-devel] [PATCH v4 widget-toolkit 10/10] APT repositories: add warnings Fabian Ebner
2021-05-10  5:54 ` [pbs-devel] [PATCH-SERIES v4] APT repositories API/UI Fabian Ebner
2021-05-10 13:22   ` Thomas Lamprecht

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210402112051.14628-5-f.ebner@proxmox.com \
    --to=f.ebner@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal