From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id EF39A1FF0AD for ; Thu, 20 Aug 2026 15:32:15 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 871AB21593; Thu, 20 Aug 2026 15:32:15 +0200 (CEST) Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=UTF-8 Date: Thu, 20 Aug 2026 15:32:05 +0200 Message-Id: Subject: Re: [PATCH proxmox v3 1/5] system-report: add crate for shared report generation From: "Lukas Wagner" To: "Erik Fastermann" , , X-Mailer: aerc 0.21.0-0-g5549850facc2-dirty References: <20260811114332.283776-1-e.fastermann@proxmox.com> <20260811114332.283776-2-e.fastermann@proxmox.com> In-Reply-To: <20260811114332.283776-2-e.fastermann@proxmox.com> X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1787232700049 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.788 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: MJQVYMIM5C727NR65UWB2A7CSTQUSZBR X-Message-ID-Hash: MJQVYMIM5C727NR65UWB2A7CSTQUSZBR X-MailFrom: l.wagner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Lukas Wagner X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Hi Erik, thanks a lot for tackling this! On Tue Aug 11, 2026 at 1:43 PM CEST, Erik Fastermann wrote: > Factor the shared report generation from PBS and PDM into a new > proxmox-system-report crate, so both generate their reports from a > single implementation. > > The crate defines the common section order (FILES, COMMANDS, > FUNCTIONS) and the set of general commands run on every product; > products pass their own additional files, commands and functions on > top. It produces no report on its own, so the user-visible changes > land in the respective server packages. > > Suggested-by: Lukas Wagner > Suggested-by: Christian Ebner > Reviewed-by: Christian Ebner > Tested-by: Christian Ebner > Signed-off-by: Erik Fastermann > --- > Cargo.toml | 2 + > proxmox-system-report/Cargo.toml | 16 ++ > proxmox-system-report/debian/control | 36 ++++ > proxmox-system-report/debian/copyright | 18 ++ > proxmox-system-report/debian/debcargo.toml | 7 + > proxmox-system-report/src/lib.rs | 239 +++++++++++++++++++++ > 6 files changed, 318 insertions(+) > create mode 100644 proxmox-system-report/Cargo.toml > create mode 100644 proxmox-system-report/debian/control > create mode 100644 proxmox-system-report/debian/copyright > create mode 100644 proxmox-system-report/debian/debcargo.toml This is missing a debian/changelog file, without it=20 `make proxmox-sytem-report-deb` does not work. [...] > +/// Generate a system report as a Markdown-like document. > +/// > +/// The report has three top-level sections, always emitted in this orde= r: > +/// `FILES`, `COMMANDS` and `FUNCTIONS`. The project-specific entries pa= ssed in > +/// are merged with a set of built-in, general-purpose entries common to= all > +/// products. > +/// > +/// Missing files, unreadable directories and commands that fail to spaw= n are > +/// reported inline instead of aborting the report. > +pub fn generate_report( > + project_files: Vec, > + project_commands: Vec, > + project_function_calls: Vec, > +) -> String { > + // We deliberately output the shared files before the project specif= ic files > + // to preserve the legacy report ordering. > + let file_contents =3D common_files() > + .iter() > + .chain(&project_files) > + .map(|group| { > + let (group, files) =3D group; > + let group_content =3D files > + .iter() > + .map(|file_name| { > + let path =3D Path::new(file_name); > + if path.is_dir() { > + get_directory_content(path) > + } else { > + get_file_content(file_name) > + } > + }) > + .collect::>() > + .join("\n\n"); > + > + format!("### {group}\n\n{group_content}") > + }) > + .collect::>() > + .join("\n\n"); > + > + let static_command_outputs =3D project_commands > + .into_iter() > + .chain(common_commands()) > + .map(|(command, args)| get_command_output(command, &args)); > + > + let dynamic_command_outputs =3D common_dynamic_commands() > + .into_iter() > + .map(|(command, args)| { > + let args: Vec<_> =3D args.iter().map(String::as_str).collect= (); > + get_command_output(command, &args) > + }); > + > + let command_outputs =3D static_command_outputs > + .chain(dynamic_command_outputs) > + .collect::>() > + .join("\n\n"); > + > + let function_outputs =3D project_function_calls > + .iter() > + .chain(&common_function_calls()) > + .map(|(desc, function)| { > + let output =3D function(); > + format!("#### {desc}\n{}\n", output.trim_end()) > + }) > + .collect::>() > + .join("\n\n"); > + > + format!( > + "## FILES\n\n{file_contents}\n## COMMANDS\n\n{command_outputs}\n= ## FUNCTIONS\n\n{function_outputs}\n" > + ) > +} Just as a side note, I think this is actually a case where a builder pattern could be very elegant, e.g. something like let report =3D SystemReport::builder() .with_common_files() .with_file("/etc/hosts") .with_common_commands() .with_command("proxmox-datacenter-manager", &["subscription", "status"]= ) ... .build(); I just wanted to mention it, from my side the current approach is also fine, don't feel obligated to change it. From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id AC0731FF0AA for ; Thu, 20 Aug 2026 15:32:16 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id E9FE5215C0; Thu, 20 Aug 2026 15:32:15 +0200 (CEST) Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=UTF-8 Date: Thu, 20 Aug 2026 15:32:05 +0200 Message-Id: Subject: Re: [PATCH proxmox v3 1/5] system-report: add crate for shared report generation From: "Lukas Wagner" To: "Erik Fastermann" , , X-Mailer: aerc 0.21.0-0-g5549850facc2-dirty References: <20260811114332.283776-1-e.fastermann@proxmox.com> <20260811114332.283776-2-e.fastermann@proxmox.com> In-Reply-To: <20260811114332.283776-2-e.fastermann@proxmox.com> X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1787232700049 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.788 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: MJQVYMIM5C727NR65UWB2A7CSTQUSZBR X-Message-ID-Hash: MJQVYMIM5C727NR65UWB2A7CSTQUSZBR X-MailFrom: l.wagner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Hi Erik, thanks a lot for tackling this! On Tue Aug 11, 2026 at 1:43 PM CEST, Erik Fastermann wrote: > Factor the shared report generation from PBS and PDM into a new > proxmox-system-report crate, so both generate their reports from a > single implementation. > > The crate defines the common section order (FILES, COMMANDS, > FUNCTIONS) and the set of general commands run on every product; > products pass their own additional files, commands and functions on > top. It produces no report on its own, so the user-visible changes > land in the respective server packages. > > Suggested-by: Lukas Wagner > Suggested-by: Christian Ebner > Reviewed-by: Christian Ebner > Tested-by: Christian Ebner > Signed-off-by: Erik Fastermann > --- > Cargo.toml | 2 + > proxmox-system-report/Cargo.toml | 16 ++ > proxmox-system-report/debian/control | 36 ++++ > proxmox-system-report/debian/copyright | 18 ++ > proxmox-system-report/debian/debcargo.toml | 7 + > proxmox-system-report/src/lib.rs | 239 +++++++++++++++++++++ > 6 files changed, 318 insertions(+) > create mode 100644 proxmox-system-report/Cargo.toml > create mode 100644 proxmox-system-report/debian/control > create mode 100644 proxmox-system-report/debian/copyright > create mode 100644 proxmox-system-report/debian/debcargo.toml This is missing a debian/changelog file, without it=20 `make proxmox-sytem-report-deb` does not work. [...] > +/// Generate a system report as a Markdown-like document. > +/// > +/// The report has three top-level sections, always emitted in this orde= r: > +/// `FILES`, `COMMANDS` and `FUNCTIONS`. The project-specific entries pa= ssed in > +/// are merged with a set of built-in, general-purpose entries common to= all > +/// products. > +/// > +/// Missing files, unreadable directories and commands that fail to spaw= n are > +/// reported inline instead of aborting the report. > +pub fn generate_report( > + project_files: Vec, > + project_commands: Vec, > + project_function_calls: Vec, > +) -> String { > + // We deliberately output the shared files before the project specif= ic files > + // to preserve the legacy report ordering. > + let file_contents =3D common_files() > + .iter() > + .chain(&project_files) > + .map(|group| { > + let (group, files) =3D group; > + let group_content =3D files > + .iter() > + .map(|file_name| { > + let path =3D Path::new(file_name); > + if path.is_dir() { > + get_directory_content(path) > + } else { > + get_file_content(file_name) > + } > + }) > + .collect::>() > + .join("\n\n"); > + > + format!("### {group}\n\n{group_content}") > + }) > + .collect::>() > + .join("\n\n"); > + > + let static_command_outputs =3D project_commands > + .into_iter() > + .chain(common_commands()) > + .map(|(command, args)| get_command_output(command, &args)); > + > + let dynamic_command_outputs =3D common_dynamic_commands() > + .into_iter() > + .map(|(command, args)| { > + let args: Vec<_> =3D args.iter().map(String::as_str).collect= (); > + get_command_output(command, &args) > + }); > + > + let command_outputs =3D static_command_outputs > + .chain(dynamic_command_outputs) > + .collect::>() > + .join("\n\n"); > + > + let function_outputs =3D project_function_calls > + .iter() > + .chain(&common_function_calls()) > + .map(|(desc, function)| { > + let output =3D function(); > + format!("#### {desc}\n{}\n", output.trim_end()) > + }) > + .collect::>() > + .join("\n\n"); > + > + format!( > + "## FILES\n\n{file_contents}\n## COMMANDS\n\n{command_outputs}\n= ## FUNCTIONS\n\n{function_outputs}\n" > + ) > +} Just as a side note, I think this is actually a case where a builder pattern could be very elegant, e.g. something like let report =3D SystemReport::builder() .with_common_files() .with_file("/etc/hosts") .with_common_commands() .with_command("proxmox-datacenter-manager", &["subscription", "status"]= ) ... .build(); I just wanted to mention it, from my side the current approach is also fine, don't feel obligated to change it.