feat: 0.6.0 — 事务/版本化/类型化/inject/run
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
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
This commit is contained in:
@@ -1,9 +1,20 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::json;
|
||||
use sqlx::PgPool;
|
||||
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,
|
||||
@@ -13,15 +24,21 @@ pub async fn run(
|
||||
) -> Result<()> {
|
||||
tracing::debug!(namespace, kind, name, "deleting record");
|
||||
|
||||
let result =
|
||||
sqlx::query("DELETE FROM secrets WHERE namespace = $1 AND kind = $2 AND name = $3")
|
||||
.bind(namespace)
|
||||
.bind(kind)
|
||||
.bind(name)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
let mut tx = pool.begin().await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
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!(
|
||||
@@ -38,23 +55,53 @@ pub async fn run(
|
||||
),
|
||||
_ => println!("Not found: [{}/{}] {}", namespace, kind, name),
|
||||
}
|
||||
} else {
|
||||
crate::audit::log(pool, "delete", namespace, kind, name, json!({})).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),
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user