[pbs-devel] [PATCH proxmox-backup v8 05/45] api/cli: add endpoint and command to check s3 client connection

Christian Ebner c.ebner at proxmox.com
Fri Jul 18 11:04:39 CEST 2025


On 7/18/25 9:42 AM, Lukas Wagner wrote:
> With the magic string replaced by constants:
> 
> Reviewed-by: Lukas Wagner <l.wagner at proxmox.com>
> 
> 
> On  2025-07-15 14:52, Christian Ebner wrote:
>> Adds a dedicated api endpoint and a proxmox-backup-manager command to
>> check if the configured S3 client can reach the bucket.
>>
>> Signed-off-by: Christian Ebner <c.ebner at proxmox.com>
>> ---
>> changes since version 7:
>> - no changes
>>
>>   src/api2/admin/mod.rs                 |  2 +
>>   src/api2/admin/s3.rs                  | 80 +++++++++++++++++++++++++++
>>   src/bin/proxmox-backup-manager.rs     |  1 +
>>   src/bin/proxmox_backup_manager/mod.rs |  2 +
>>   src/bin/proxmox_backup_manager/s3.rs  | 46 +++++++++++++++
>>   5 files changed, 131 insertions(+)
>>   create mode 100644 src/api2/admin/s3.rs
>>   create mode 100644 src/bin/proxmox_backup_manager/s3.rs
>>
>> diff --git a/src/api2/admin/mod.rs b/src/api2/admin/mod.rs
>> index a1c49f8e2..7694de4b9 100644
>> --- a/src/api2/admin/mod.rs
>> +++ b/src/api2/admin/mod.rs
>> @@ -9,6 +9,7 @@ pub mod gc;
>>   pub mod metrics;
>>   pub mod namespace;
>>   pub mod prune;
>> +pub mod s3;
>>   pub mod sync;
>>   pub mod traffic_control;
>>   pub mod verify;
>> @@ -19,6 +20,7 @@ const SUBDIRS: SubdirMap = &sorted!([
>>       ("metrics", &metrics::ROUTER),
>>       ("prune", &prune::ROUTER),
>>       ("gc", &gc::ROUTER),
>> +    ("s3", &s3::ROUTER),
>>       ("sync", &sync::ROUTER),
>>       ("traffic-control", &traffic_control::ROUTER),
>>       ("verify", &verify::ROUTER),
>> diff --git a/src/api2/admin/s3.rs b/src/api2/admin/s3.rs
>> new file mode 100644
>> index 000000000..d20031707
>> --- /dev/null
>> +++ b/src/api2/admin/s3.rs
>> @@ -0,0 +1,80 @@
>> +//! S3 bucket operations
>> +
>> +use anyhow::{Context, Error};
>> +use serde_json::Value;
>> +
>> +use proxmox_http::Body;
>> +use proxmox_router::{list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap};
>> +use proxmox_s3_client::{
>> +    S3Client, S3ClientConfig, S3ClientOptions, S3ClientSecretsConfig, S3_BUCKET_NAME_SCHEMA,
>> +    S3_CLIENT_ID_SCHEMA,
>> +};
>> +use proxmox_schema::*;
>> +use proxmox_sortable_macro::sortable;
>> +
>> +use pbs_api_types::PRIV_SYS_MODIFY;
>> +
>> +#[api(
>> +    input: {
>> +        properties: {
>> +            "s3-client-id": {
>> +                schema: S3_CLIENT_ID_SCHEMA,
>> +            },
>> +            bucket: {
>> +                schema: S3_BUCKET_NAME_SCHEMA,
>> +            },
>> +            "store-prefix": {
>> +                type: String,
>> +                description: "Store prefix within bucket for S3 object keys (commonly datastore name)",
>> +            },
>> +        },
>> +    },
>> +    access: {
>> +        permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
>> +    },
>> +)]
>> +/// Perform basic sanity check for given s3 client configuration
>> +pub async fn check(
>> +    s3_client_id: String,
>> +    bucket: String,
>> +    store_prefix: String,
>> +    _rpcenv: &mut dyn RpcEnvironment,
>> +) -> Result<Value, Error> {
>> +    let (config, _digest) = pbs_config::s3::config()?;
>> +    let config: S3ClientConfig = config
>> +        .lookup("s3client", &s3_client_id)
>> +        .context("config lookup failed")?;
>> +    let (secrets, _secrets_digest) = pbs_config::s3::secrets_config()?;
>> +    let secrets: S3ClientSecretsConfig = secrets
>> +        .lookup("s3secrets", &s3_client_id)
>> +        .context("secrets lookup failed")?;
> 
> Same thing here with regards to the section config type strings.

Adapted both to the new constants as well

>> +
>> +    let options = S3ClientOptions::from_config(config, secrets, bucket, store_prefix);
>> +
>> +    let test_object_key = ".s3-client-test";
>> +    let client = S3Client::new(options).context("client creation failed")?;
>> +    client.head_bucket().await.context("head object failed")?;
>> +    client
>> +        .put_object(test_object_key.into(), Body::empty(), true)
>> +        .await
>> +        .context("put object failed")?;
>> +    client
>> +        .get_object(test_object_key.into())
>> +        .await
>> +        .context("get object failed")?;
>> +    client
>> +        .delete_object(test_object_key.into())
>> +        .await
>> +        .context("delete object failed")?;
>> +
>> +    Ok(Value::Null)
>> +}
>> +
>> +#[sortable]
>> +const S3_OPERATION_SUBDIRS: SubdirMap = &[("check", &Router::new().get(&API_METHOD_CHECK))];
>> +
>> +const S3_OPERATION_ROUTER: Router = Router::new()
>> +    .get(&list_subdirs_api_method!(S3_OPERATION_SUBDIRS))
>> +    .subdirs(S3_OPERATION_SUBDIRS);
>> +
>> +pub const ROUTER: Router = Router::new().match_all("s3-client-id", &S3_OPERATION_ROUTER);
>> diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs
>> index d4363e717..68d87c676 100644
>> --- a/src/bin/proxmox-backup-manager.rs
>> +++ b/src/bin/proxmox-backup-manager.rs
>> @@ -677,6 +677,7 @@ async fn run() -> Result<(), Error> {
>>           .insert("garbage-collection", garbage_collection_commands())
>>           .insert("acme", acme_mgmt_cli())
>>           .insert("cert", cert_mgmt_cli())
>> +        .insert("s3", s3_commands())
>>           .insert("subscription", subscription_commands())
>>           .insert("sync-job", sync_job_commands())
>>           .insert("verify-job", verify_job_commands())
>> diff --git a/src/bin/proxmox_backup_manager/mod.rs b/src/bin/proxmox_backup_manager/mod.rs
>> index 9b5c73e9a..312a6db6b 100644
>> --- a/src/bin/proxmox_backup_manager/mod.rs
>> +++ b/src/bin/proxmox_backup_manager/mod.rs
>> @@ -26,6 +26,8 @@ mod prune;
>>   pub use prune::*;
>>   mod remote;
>>   pub use remote::*;
>> +mod s3;
>> +pub use s3::*;
>>   mod subscription;
>>   pub use subscription::*;
>>   mod sync;
>> diff --git a/src/bin/proxmox_backup_manager/s3.rs b/src/bin/proxmox_backup_manager/s3.rs
>> new file mode 100644
>> index 000000000..9bb89ff55
>> --- /dev/null
>> +++ b/src/bin/proxmox_backup_manager/s3.rs
>> @@ -0,0 +1,46 @@
>> +use proxmox_router::{cli::*, RpcEnvironment};
>> +use proxmox_s3_client::{S3_BUCKET_NAME_SCHEMA, S3_CLIENT_ID_SCHEMA};
>> +use proxmox_schema::api;
>> +
>> +use proxmox_backup::api2;
>> +
>> +use anyhow::Error;
>> +use serde_json::Value;
>> +
>> +#[api(
>> +    input: {
>> +        properties: {
>> +            "s3-client-id": {
>> +                schema: S3_CLIENT_ID_SCHEMA,
>> +            },
>> +            bucket: {
>> +                schema: S3_BUCKET_NAME_SCHEMA,
>> +            },
>> +            "store-prefix": {
>> +                type: String,
>> +                description: "Store prefix within bucket for S3 object keys (commonly datastore name)",
>> +            },
>> +        },
>> +    },
>> +)]
>> +/// Perform basic sanity checks for given S3 client configuration
>> +async fn check(
>> +    s3_client_id: String,
>> +    bucket: String,
>> +    store_prefix: String,
>> +    rpcenv: &mut dyn RpcEnvironment,
>> +) -> Result<Value, Error> {
>> +    api2::admin::s3::check(s3_client_id, bucket, store_prefix, rpcenv).await?;
>> +    Ok(Value::Null)
>> +}
>> +
>> +pub fn s3_commands() -> CommandLineInterface {
>> +    let cmd_def = CliCommandMap::new().insert(
>> +        "check",
>> +        CliCommand::new(&API_METHOD_CHECK)
>> +            .arg_param(&["s3-client-id", "bucket"])
>> +            .completion_cb("s3-client-id", pbs_config::s3::complete_s3_client_id),
>> +    );
>> +
>> +    cmd_def.into()
>> +}
> 





More information about the pbs-devel mailing list