public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Stefan Sterz <s.sterz@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox-offline-mirror 4/6] helper: improve handling of multiple keys when activating them
Date: Thu,  9 Nov 2023 16:34:01 +0100	[thread overview]
Message-ID: <20231109153403.529870-5-s.sterz@proxmox.com> (raw)
In-Reply-To: <20231109153403.529870-1-s.sterz@proxmox.com>

this commit fixes a behavior where pom would applied any subscription
key that matched the provided product. it did not check whether the
server id of the activated subscription matched the current system.
this commit fixes that and only allows applying subscriptions for the
current system.

it also adds a couple of ux improvements:

- the `offline-key` sub-command now does not require the `--product`
  parameter anymore. if there are multiple keys with different
  products for the same server we will try to activate them all. the
  assumption is that the user added all keys intentionally (e.g. a
  combo pbs+pve system) and would like to activate them all at once.
  since this only makes the api more permissive this shouldn't be a
  breaking change.
- if the `offline-key` sub-command encounters multiple subscription
  keys with the same product and server id, it only activates the one
  with the due date furthest in the future. this makes sense in a
  scenario where a user simply adds new subscription keys to their
  key medium without removing older ones (perhaps older subscriptions
  haven't even expired just yet).
- the interactive `setup` sub-command now only offers keys that have a
  matching server id. it also orders them in such a way that the top
  most key for a given product has the next due date furthest in the
  future.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
---
 src/bin/proxmox-offline-mirror-helper.rs | 97 ++++++++++++++----------
 1 file changed, 59 insertions(+), 38 deletions(-)

diff --git a/src/bin/proxmox-offline-mirror-helper.rs b/src/bin/proxmox-offline-mirror-helper.rs
index f1b4bd7..a231e84 100644
--- a/src/bin/proxmox-offline-mirror-helper.rs
+++ b/src/bin/proxmox-offline-mirror-helper.rs
@@ -24,7 +24,7 @@ use proxmox_offline_mirror::helpers::tty::{
 use proxmox_offline_mirror::medium::{self, generate_repo_snippet, MediumState};

 fn set_subscription_key(
-    product: ProductType,
+    product: &ProductType,
     subscription: &SubscriptionInfo,
 ) -> Result<String, Error> {
     let data = base64::encode(serde_json::to_vec(subscription)?);
@@ -218,32 +218,34 @@ async fn setup(_param: Value) -> Result<(), Error> {
             }
             Action::UpdateOfflineSubscription => {
                 let server_id = proxmox_subscription::get_hardware_address()?;
-                let subscriptions: Vec<(&SubscriptionInfo, &str)> = state
+                let mut subscriptions: Vec<((ProductType, &SubscriptionInfo), &str)> = state
                     .subscriptions
                     .iter()
-                    .filter_map(|s| {
-                        if let Some(key) = s.key.as_ref() {
-                            if let Ok(product) = key[..3].parse::<ProductType>() {
-                                if product == ProductType::Pom {
-                                    return None;
-                                } else {
-                                    return Some((s, key.as_str()));
-                                }
-                            }
-                        }
-                        None
+                    .filter(|sub| sub.serverid.as_ref() == Some(&server_id))
+                    .filter_map(|sub| {
+                        sub.get_product_type()
+                            .ok()
+                            .and_then(|prod| Some(((prod, sub), sub.key.as_ref()?.as_str())))
                     })
+                    .filter(|((found_product, _), _)| found_product != &ProductType::Pom)
                     .collect();

+                subscriptions.sort_by(|((prod_a, sub_a), _), ((prod_b, sub_b), _)| {
+                    if prod_a == prod_b {
+                        return sub_b
+                            .get_next_due_date()
+                            .ok()
+                            .cmp(&sub_a.get_next_due_date().ok());
+                    }
+
+                    prod_a.cmp(prod_b)
+                });
+
                 if subscriptions.is_empty() {
-                    println!(
-                        "No matching subscription key found for server ID '{}'",
-                        server_id
-                    );
+                    println!("No matching subscription key found for server ID '{server_id}'");
                 } else {
-                    let info = read_selection_from_tty("Select key", &subscriptions, None)?;
-                    // safe unwrap, checked above!
-                    let product: ProductType = info.key.as_ref().unwrap()[..3].parse()?;
+                    let (product, info) =
+                        read_selection_from_tty("Select key", &subscriptions, None)?;
                     set_subscription_key(product, info)?;
                 }
             }
@@ -265,6 +267,7 @@ async fn setup(_param: Value) -> Result<(), Error> {
             },
             product: {
                 type: ProductType,
+                optional: true,
             },
         },
     },
@@ -272,10 +275,10 @@ async fn setup(_param: Value) -> Result<(), Error> {
 /// Configures and offline subscription key
 async fn setup_offline_key(
     mountpoint: String,
-    product: ProductType,
+    product: Option<ProductType>,
     _param: Value,
 ) -> Result<(), Error> {
-    if product == ProductType::Pom {
+    if product == Some(ProductType::Pom) {
         param_bail!(
             "product",
             format_err!("Proxmox Offline Mirror does not support offline operations.")
@@ -299,27 +302,45 @@ async fn setup_offline_key(
     );

     let server_id = proxmox_subscription::get_hardware_address()?;
-    let subscription = state.subscriptions.iter().find(|s| {
-        if let Some(key) = s.key.as_ref() {
-            if let Ok(found_product) = key[..3].parse::<ProductType>() {
-                return product == found_product;
-            }
+
+    let mut subscriptions: Vec<(ProductType, &SubscriptionInfo)> = state
+        .subscriptions
+        .iter()
+        .filter(|sub| sub.serverid.as_ref() == Some(&server_id))
+        .filter_map(|sub| sub.get_product_type().ok().map(|prod| (prod, sub)))
+        .filter(|(found_product, _)| {
+            (product.is_none() || Some(found_product) == product.as_ref())
+                && found_product != &ProductType::Pom
+        })
+        .collect();
+
+    if subscriptions.is_empty() {
+        bail!("No matching subscription key found for server ID '{server_id}'");
+    }
+
+    subscriptions.sort_by(|(prod_a, sub_a), (prod_b, sub_b)| {
+        if prod_a == prod_b {
+            return sub_b
+                .get_next_due_date()
+                .ok()
+                .cmp(&sub_a.get_next_due_date().ok());
         }
-        false
+
+        prod_a.cmp(prod_b)
     });

-    match subscription {
-        Some(subscription) => {
-            eprintln!("Setting offline subscription key for {product}..");
-            match set_subscription_key(product, subscription) {
-                Ok(output) if !output.is_empty() => eprintln!("success: {output}"),
-                Ok(_) => eprintln!("success."),
-                Err(err) => eprintln!("error: {err}"),
-            }
-            Ok(())
+    subscriptions.dedup_by(|(prod_a, _), (prod_b, _)| prod_a == prod_b);
+
+    for (product, subscription) in subscriptions {
+        eprintln!("Setting offline subscription key for {product}...");
+        match set_subscription_key(&product, subscription) {
+            Ok(output) if !output.is_empty() => eprintln!("success: {output}"),
+            Ok(_) => eprintln!("success."),
+            Err(err) => eprintln!("error: {err}"),
         }
-        None => bail!("No matching subscription key found for product '{product}' and server ID '{server_id}'"),
     }
+
+    Ok(())
 }

 #[api(
--
2.39.2





  parent reply	other threads:[~2023-11-09 15:34 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-09 15:33 [pbs-devel] [PATCH offline-mirror/proxmox/backup-server 0/6] improve pom multi-key handling and pbs key check Stefan Sterz
2023-11-09 15:33 ` [pbs-devel] [PATCH proxmox 1/6] type: move `ProductType` type to `proxmox-subscription` from pom Stefan Sterz
2023-11-27 13:12   ` [pbs-devel] applied: " Fabian Grünbichler
2023-11-09 15:33 ` [pbs-devel] [PATCH proxmox 2/6] subscription: expose the `next_due_date` as an `i64` Stefan Sterz
2023-11-27 13:12   ` [pbs-devel] applied: " Fabian Grünbichler
2023-11-09 15:34 ` [pbs-devel] [PATCH proxmox-offline-mirror 3/6] type: move `ProductType` enum to `proxmox-subscription` Stefan Sterz
2023-11-09 15:34 ` Stefan Sterz [this message]
2023-11-27 13:10   ` [pbs-devel] [PATCH proxmox-offline-mirror 4/6] helper: improve handling of multiple keys when activating them Fabian Grünbichler
2023-11-09 15:34 ` [pbs-devel] [PATCH proxmox-offline-mirror 5/6] offline mirror binary: rustfmt clean up Stefan Sterz
2023-11-09 15:34 ` [pbs-devel] [PATCH proxmox-backup 6/6] manager: check if offline subscription is for the correct product Stefan Sterz
2023-11-27 13:14   ` [pbs-devel] applied: " Fabian Grünbichler

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=20231109153403.529870-5-s.sterz@proxmox.com \
    --to=s.sterz@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal