Some checks failed
Secrets CLI - Build & Release / 版本 & Release (push) Successful in 2s
Secrets CLI - Build & Release / 质量检查 (fmt / clippy / test) (push) Successful in 1m37s
Secrets CLI - Build & Release / Build (aarch64-apple-darwin) (push) Successful in 37s
Secrets CLI - Build & Release / Build (x86_64-unknown-linux-musl) (push) Successful in 50s
Secrets CLI - Build & Release / 发布草稿 Release (push) Successful in 2s
Secrets CLI - Build & Release / Build (x86_64-pc-windows-msvc) (push) Has been cancelled
- 写路径事务化:add/update/delete 与 audit 同事务,update CAS 并发保护 - 版本化与回滚:secrets_history 表、version 字段、history/rollback 命令 - 类型化字段:key:=<json> 支持数字、布尔、数组、对象 - 临时 env 模式:inject 输出 KEY=VALUE,run 向子进程注入 - inject/run 至少需一个过滤条件;search -o env 使用 shell_quote;JSON 输出含 version Made-with: Cursor
108 lines
2.8 KiB
Rust
108 lines
2.8 KiB
Rust
use anyhow::Result;
|
|
use serde_json::{Value, json};
|
|
use sqlx::{FromRow, PgPool};
|
|
use uuid::Uuid;
|
|
|
|
use crate::db;
|
|
use crate::output::OutputMode;
|
|
|
|
#[derive(FromRow)]
|
|
struct DeleteRow {
|
|
id: Uuid,
|
|
version: i64,
|
|
tags: Vec<String>,
|
|
metadata: Value,
|
|
encrypted: Vec<u8>,
|
|
}
|
|
|
|
pub async fn run(
|
|
pool: &PgPool,
|
|
namespace: &str,
|
|
kind: &str,
|
|
name: &str,
|
|
output: OutputMode,
|
|
) -> Result<()> {
|
|
tracing::debug!(namespace, kind, name, "deleting record");
|
|
|
|
let mut tx = pool.begin().await?;
|
|
|
|
let row: Option<DeleteRow> = sqlx::query_as(
|
|
"SELECT id, version, tags, metadata, encrypted FROM secrets \
|
|
WHERE namespace = $1 AND kind = $2 AND name = $3 \
|
|
FOR UPDATE",
|
|
)
|
|
.bind(namespace)
|
|
.bind(kind)
|
|
.bind(name)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
|
|
let Some(row) = row else {
|
|
tx.rollback().await?;
|
|
tracing::warn!(namespace, kind, name, "record not found for deletion");
|
|
match output {
|
|
OutputMode::Json => println!(
|
|
"{}",
|
|
serde_json::to_string_pretty(
|
|
&json!({"action":"not_found","namespace":namespace,"kind":kind,"name":name})
|
|
)?
|
|
),
|
|
OutputMode::JsonCompact => println!(
|
|
"{}",
|
|
serde_json::to_string(
|
|
&json!({"action":"not_found","namespace":namespace,"kind":kind,"name":name})
|
|
)?
|
|
),
|
|
_ => println!("Not found: [{}/{}] {}", namespace, kind, name),
|
|
}
|
|
return Ok(());
|
|
};
|
|
|
|
// Snapshot before physical delete so the row can be restored via rollback.
|
|
if let Err(e) = db::snapshot_history(
|
|
&mut tx,
|
|
db::SnapshotParams {
|
|
secret_id: row.id,
|
|
namespace,
|
|
kind,
|
|
name,
|
|
version: row.version,
|
|
action: "delete",
|
|
tags: &row.tags,
|
|
metadata: &row.metadata,
|
|
encrypted: &row.encrypted,
|
|
},
|
|
)
|
|
.await
|
|
{
|
|
tracing::warn!(error = %e, "failed to snapshot history before delete");
|
|
}
|
|
|
|
sqlx::query("DELETE FROM secrets WHERE id = $1")
|
|
.bind(row.id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
crate::audit::log_tx(&mut tx, "delete", namespace, kind, name, json!({})).await;
|
|
|
|
tx.commit().await?;
|
|
|
|
match output {
|
|
OutputMode::Json => println!(
|
|
"{}",
|
|
serde_json::to_string_pretty(
|
|
&json!({"action":"deleted","namespace":namespace,"kind":kind,"name":name})
|
|
)?
|
|
),
|
|
OutputMode::JsonCompact => println!(
|
|
"{}",
|
|
serde_json::to_string(
|
|
&json!({"action":"deleted","namespace":namespace,"kind":kind,"name":name})
|
|
)?
|
|
),
|
|
_ => println!("Deleted: [{}/{}] {}", namespace, kind, name),
|
|
}
|
|
|
|
Ok(())
|
|
}
|