From b4900286ce88285148f5ece0a061a6fbccbd1d72 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Thu, 21 May 2020 08:31:16 +0200 Subject: [PATCH] src/config/jobs.rs: use SectionConfig for jobs --- src/api2/pull.rs | 10 +- src/api2/types.rs | 11 ++ src/config/jobs.rs | 245 +++++++++++++++++++-------------------------- 3 files changed, 120 insertions(+), 146 deletions(-) diff --git a/src/api2/pull.rs b/src/api2/pull.rs index f0c42b5c..4cb1f21c 100644 --- a/src/api2/pull.rs +++ b/src/api2/pull.rs @@ -394,11 +394,9 @@ pub async fn pull_store( "remote-store": { schema: DATASTORE_SCHEMA, }, - delete: { - description: "Delete vanished backups. This remove the local copy if the remote backup was deleted.", - type: Boolean, + "remove-vanished": { + schema: REMOVE_VANISHED_BACKUPS_SCHEMA, optional: true, - default: true, }, }, }, @@ -416,7 +414,7 @@ async fn pull ( store: String, remote: String, remote_store: String, - delete: Option, + remove_vanished: Option, _info: &ApiMethod, rpcenv: &mut dyn RpcEnvironment, ) -> Result { @@ -427,7 +425,7 @@ async fn pull ( user_info.check_privs(&username, &["datastore", &store], PRIV_DATASTORE_BACKUP, false)?; user_info.check_privs(&username, &["remote", &remote, &remote_store], PRIV_REMOTE_READ, false)?; - let delete = delete.unwrap_or(true); + let delete = remove_vanished.unwrap_or(true); if delete { user_info.check_privs(&username, &["datastore", &store], PRIV_DATASTORE_PRUNE, false)?; diff --git a/src/api2/types.rs b/src/api2/types.rs index 4ec95ef1..85a647dc 100644 --- a/src/api2/types.rs +++ b/src/api2/types.rs @@ -303,6 +303,17 @@ pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.") .max_length(32) .schema(); +pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.") + .format(&PROXMOX_SAFE_ID_FORMAT) + .min_length(3) + .max_length(32) + .schema(); + +pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new( + "Delete vanished backups. This remove the local copy if the remote backup was deleted.") + .default(true) + .schema(); + pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).") .format(&SINGLE_LINE_COMMENT_FORMAT) .schema(); diff --git a/src/config/jobs.rs b/src/config/jobs.rs index 129cf22f..a9958f28 100644 --- a/src/config/jobs.rs +++ b/src/config/jobs.rs @@ -1,160 +1,125 @@ use anyhow::{bail, Error}; -use regex::Regex; use lazy_static::lazy_static; +use std::collections::HashMap; +use serde::{Serialize, Deserialize}; -use proxmox::api::section_config::SectionConfigData; - -use crate::PROXMOX_SAFE_ID_REGEX_STR; -use crate::tools::systemd::config::*; -use crate::tools::systemd::types::*; - -const SYSTEMD_CONFIG_DIR: &str = "/etc/systemd/system"; - -#[derive(Debug)] -pub enum JobType { - GarbageCollection, - Prune, -} - -#[derive(Debug)] -pub struct CalenderTimeSpec { - pub hour: u8, // 0-23 -} - -#[derive(Debug)] -pub struct JobListEntry { - job_type: JobType, - id: String, -} - -pub fn list_jobs() -> Result, Error> { - - lazy_static!{ - static ref PBS_JOB_REGEX: Regex = Regex::new( - concat!(r"^pbs-(gc|prune)-(", PROXMOX_SAFE_ID_REGEX_STR!(), ").timer$") - ).unwrap(); +use proxmox::api::{ + api, + schema::*, + section_config::{ + SectionConfig, + SectionConfigData, + SectionConfigPlugin, } +}; - let mut list = Vec::new(); +use proxmox::tools::{fs::replace_file, fs::CreateOptions}; - for entry in crate::tools::fs::read_subdir(libc::AT_FDCWD, SYSTEMD_CONFIG_DIR)? { - let entry = entry?; - let file_type = match entry.file_type() { - Some(file_type) => file_type, - None => bail!("unable to detect file type"), - }; - if file_type != nix::dir::Type::File { continue; }; +use crate::api2::types::*; - let file_name = entry.file_name().to_bytes(); - if file_name == b"." || file_name == b".." { continue; }; +lazy_static! { + static ref CONFIG: SectionConfig = init(); +} - let name = match std::str::from_utf8(file_name) { - Ok(name) => name, - Err(_) => continue, - }; - let caps = match PBS_JOB_REGEX.captures(name) { - Some(caps) => caps, - None => continue, - }; - // fixme: read config data ? - //let config = parse_systemd_timer(&format!("{}/{}", SYSTEMD_CONFIG_DIR, name))?; +#[api( + properties: { + id: { + schema: JOB_ID_SCHEMA, + }, + store: { + schema: DATASTORE_SCHEMA, + }, + remote: { + schema: REMOTE_ID_SCHEMA, + }, + "remote-store": { + schema: DATASTORE_SCHEMA, + }, + "remove-vanished": { + schema: REMOVE_VANISHED_BACKUPS_SCHEMA, + optional: true, + }, + comment: { + optional: true, + schema: SINGLE_LINE_COMMENT_SCHEMA, + }, + schedule: { + optional: true, + schema: GC_SCHEDULE_SCHEMA, + }, + } +)] +#[serde(rename_all="kebab-case")] +#[derive(Serialize,Deserialize)] +/// Pull Job +pub struct PullJobConfig { + pub id: String, + pub store: String, + pub remote: String, + pub remote_store: String, + #[serde(skip_serializing_if="Option::is_none")] + pub remove_vanished: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub comment: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub schedule: Option, +} - match (&caps[1], &caps[2]) { - ("gc", store) => { - list.push(JobListEntry { - job_type: JobType::GarbageCollection, - id: store.to_string(), - }); +fn init() -> SectionConfig { + let obj_schema = match PullJobConfig::API_SCHEMA { + Schema::Object(ref obj_schema) => obj_schema, + _ => unreachable!(), + }; + + let plugin = SectionConfigPlugin::new("pull".to_string(), Some(String::from("id")), obj_schema); + let mut config = SectionConfig::new(&JOB_ID_SCHEMA); + config.register_plugin(plugin); + + config +} + +pub const JOB_CFG_FILENAME: &str = "/etc/proxmox-backup/job.cfg"; +pub const JOB_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.job.lck"; + +pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { + let content = match std::fs::read_to_string(JOB_CFG_FILENAME) { + Ok(c) => c, + Err(err) => { + if err.kind() == std::io::ErrorKind::NotFound { + String::from("") + } else { + bail!("unable to read '{}' - {}", JOB_CFG_FILENAME, err); } - ("prune", store) => { - list.push(JobListEntry { - job_type: JobType::Prune, - id: store.to_string(), - }); - } - _ => unreachable!(), } - } + }; - Ok(list) + let digest = openssl::sha::sha256(content.as_bytes()); + let data = CONFIG.parse(JOB_CFG_FILENAME, &content)?; + Ok((data, digest)) } -pub fn new_systemd_service_config( - unit: &SystemdUnitSection, - service: &SystemdServiceSection, -) -> Result { +pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { + let raw = CONFIG.write(JOB_CFG_FILENAME, &config)?; - let mut config = SectionConfigData::new(); - config.set_data("Unit", "Unit", unit)?; - config.record_order("Unit"); - config.set_data("Service", "Service", service)?; - config.record_order("Service"); + let backup_user = crate::backup::backup_user()?; + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); + // set the correct owner/group/permissions while saving file + // owner(rw) = root, group(r)= backup + let options = CreateOptions::new() + .perm(mode) + .owner(nix::unistd::ROOT) + .group(backup_user.gid); - Ok(config) -} - -pub fn new_systemd_timer_config( - unit: &SystemdUnitSection, - timer: &SystemdTimerSection, - install: &SystemdInstallSection, -) -> Result { - - let mut config = SectionConfigData::new(); - config.set_data("Unit", "Unit", unit)?; - config.record_order("Unit"); - config.set_data("Timer", "Timer", timer)?; - config.record_order("Timer"); - config.set_data("Install", "Install", install)?; - config.record_order("Install"); - - Ok(config) -} - -pub fn create_garbage_collection_job( - schedule: CalenderTimeSpec, - store: &str, -) -> Result<(), Error> { - - if schedule.hour > 23 { - bail!("inavlid time spec: hour > 23"); - } - - let description = format!("Proxmox Backup Server Garbage Collection Job '{}'", store); - - let unit = SystemdUnitSection { - Description: description.clone(), - ConditionACPower: Some(true), - ..Default::default() - }; - - let cmd = format!("/usr/sbin/proxmox-backup-manager garbage-collection start {} --output-format json", store); - let service = SystemdServiceSection { - Type: Some(ServiceStartup::Oneshot), - ExecStart: Some(vec![cmd]), - ..Default::default() - }; - - let service_config = new_systemd_service_config(&unit, &service)?; - - let timer = SystemdTimerSection { - OnCalendar: Some(vec![format!("{}:00", schedule.hour)]), - ..Default::default() - }; - - let install = SystemdInstallSection { - WantedBy: Some(vec![String::from("timers.target")]), - ..Default::default() - }; - - let timer_config = new_systemd_timer_config(&unit, &timer, &install)?; - - let basename = format!("{}/pbs-gc-{}", SYSTEMD_CONFIG_DIR, store); - let timer_fn = format!("{}.timer", basename); - let service_fn = format!("{}.service", basename); - - save_systemd_service(&service_fn, &service_config)?; - save_systemd_timer(&timer_fn, &timer_config)?; + replace_file(JOB_CFG_FILENAME, raw.as_bytes(), options)?; Ok(()) } + +// shell completion helper +pub fn complete_job_id(_arg: &str, _param: &HashMap) -> Vec { + match config() { + Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), + Err(_) => return vec![], + } +}