Skip to content

Commit 96ec0cd

Browse files
committed
refactor(server): move policy persistence into policy_store
1 parent cd360d0 commit 96ec0cd

7 files changed

Lines changed: 386 additions & 314 deletions

File tree

crates/openshell-server/src/grpc/policy.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
use crate::ServerState;
1414
use crate::persistence::{DraftChunkRecord, ObjectId, ObjectName, PolicyRecord, Store};
15+
use crate::policy_store::PolicyStoreExt;
1516
use openshell_core::proto::policy_merge_operation;
1617
use openshell_core::proto::setting_value;
1718
use openshell_core::proto::{

crates/openshell-server/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod http;
2727
mod inference;
2828
mod multiplex;
2929
mod persistence;
30+
pub(crate) mod policy_store;
3031
mod sandbox_index;
3132
mod sandbox_watch;
3233
mod ssh_tunnel;

crates/openshell-server/src/persistence/mod.rs

Lines changed: 1 addition & 308 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,7 @@ pub use openshell_core::proto::{
1010
StoredDraftChunk as DraftChunkRecord, StoredPolicyRevision as PolicyRecord,
1111
};
1212

13-
use openshell_core::{
14-
Error as CoreError, Result as CoreResult,
15-
proto::{
16-
DraftChunkPayload, NetworkPolicyRule, PolicyRevisionPayload,
17-
SandboxPolicy as ProtoSandboxPolicy,
18-
},
19-
};
13+
use openshell_core::{Error as CoreError, Result as CoreResult};
2014
use prost::Message;
2115
use rand::Rng;
2216
use std::time::{SystemTime, UNIX_EPOCH};
@@ -228,122 +222,6 @@ impl Store {
228222
}
229223
}
230224

231-
// -----------------------------------------------------------------------
232-
// Policy revision operations
233-
// -----------------------------------------------------------------------
234-
/// Insert a new policy revision.
235-
pub async fn put_policy_revision(
236-
&self,
237-
id: &str,
238-
sandbox_id: &str,
239-
version: i64,
240-
payload: &[u8],
241-
hash: &str,
242-
) -> PersistenceResult<()> {
243-
match self {
244-
Self::Postgres(store) => {
245-
store
246-
.put_policy_revision(id, sandbox_id, version, payload, hash)
247-
.await
248-
}
249-
Self::Sqlite(store) => {
250-
store
251-
.put_policy_revision(id, sandbox_id, version, payload, hash)
252-
.await
253-
}
254-
}
255-
}
256-
257-
/// Get the latest policy revision for a sandbox (by highest version, any status).
258-
pub async fn get_latest_policy(
259-
&self,
260-
sandbox_id: &str,
261-
) -> PersistenceResult<Option<PolicyRecord>> {
262-
match self {
263-
Self::Postgres(store) => store.get_latest_policy(sandbox_id).await,
264-
Self::Sqlite(store) => store.get_latest_policy(sandbox_id).await,
265-
}
266-
}
267-
268-
/// Get the latest loaded policy revision for a sandbox.
269-
pub async fn get_latest_loaded_policy(
270-
&self,
271-
sandbox_id: &str,
272-
) -> PersistenceResult<Option<PolicyRecord>> {
273-
match self {
274-
Self::Postgres(store) => store.get_latest_loaded_policy(sandbox_id).await,
275-
Self::Sqlite(store) => store.get_latest_loaded_policy(sandbox_id).await,
276-
}
277-
}
278-
279-
/// Get a specific policy revision by sandbox id and version.
280-
pub async fn get_policy_by_version(
281-
&self,
282-
sandbox_id: &str,
283-
version: i64,
284-
) -> PersistenceResult<Option<PolicyRecord>> {
285-
match self {
286-
Self::Postgres(store) => store.get_policy_by_version(sandbox_id, version).await,
287-
Self::Sqlite(store) => store.get_policy_by_version(sandbox_id, version).await,
288-
}
289-
}
290-
291-
/// List policy revisions for a sandbox, ordered by version descending.
292-
pub async fn list_policies(
293-
&self,
294-
sandbox_id: &str,
295-
limit: u32,
296-
offset: u32,
297-
) -> PersistenceResult<Vec<PolicyRecord>> {
298-
match self {
299-
Self::Postgres(store) => store.list_policies(sandbox_id, limit, offset).await,
300-
Self::Sqlite(store) => store.list_policies(sandbox_id, limit, offset).await,
301-
}
302-
}
303-
304-
/// Update the status of a policy revision.
305-
pub async fn update_policy_status(
306-
&self,
307-
sandbox_id: &str,
308-
version: i64,
309-
status: &str,
310-
load_error: Option<&str>,
311-
loaded_at_ms: Option<i64>,
312-
) -> PersistenceResult<bool> {
313-
match self {
314-
Self::Postgres(store) => {
315-
store
316-
.update_policy_status(sandbox_id, version, status, load_error, loaded_at_ms)
317-
.await
318-
}
319-
Self::Sqlite(store) => {
320-
store
321-
.update_policy_status(sandbox_id, version, status, load_error, loaded_at_ms)
322-
.await
323-
}
324-
}
325-
}
326-
327-
/// Mark all pending and loaded policy revisions older than `before_version` as superseded.
328-
pub async fn supersede_older_policies(
329-
&self,
330-
sandbox_id: &str,
331-
before_version: i64,
332-
) -> PersistenceResult<u64> {
333-
match self {
334-
Self::Postgres(store) => {
335-
store
336-
.supersede_older_policies(sandbox_id, before_version)
337-
.await
338-
}
339-
Self::Sqlite(store) => {
340-
store
341-
.supersede_older_policies(sandbox_id, before_version)
342-
.await
343-
}
344-
}
345-
}
346-
347225
// -----------------------------------------------------------------------
348226
// Generic protobuf message helpers
349227
// -----------------------------------------------------------------------
@@ -402,191 +280,6 @@ impl Store {
402280
.map(Some)
403281
.map_err(|e| PersistenceError::Decode(format!("protobuf decode error: {e}")))
404282
}
405-
406-
/// Insert or merge a new draft policy chunk.
407-
pub async fn put_draft_chunk(&self, chunk: &DraftChunkRecord) -> PersistenceResult<()> {
408-
match self {
409-
Self::Postgres(store) => store.put_draft_chunk(chunk).await,
410-
Self::Sqlite(store) => store.put_draft_chunk(chunk).await,
411-
}
412-
}
413-
414-
/// Fetch a single draft chunk by id.
415-
pub async fn get_draft_chunk(&self, id: &str) -> PersistenceResult<Option<DraftChunkRecord>> {
416-
match self {
417-
Self::Postgres(store) => store.get_draft_chunk(id).await,
418-
Self::Sqlite(store) => store.get_draft_chunk(id).await,
419-
}
420-
}
421-
422-
/// List draft chunks for a sandbox, optionally filtered by status.
423-
pub async fn list_draft_chunks(
424-
&self,
425-
sandbox_id: &str,
426-
status_filter: Option<&str>,
427-
) -> PersistenceResult<Vec<DraftChunkRecord>> {
428-
match self {
429-
Self::Postgres(store) => store.list_draft_chunks(sandbox_id, status_filter).await,
430-
Self::Sqlite(store) => store.list_draft_chunks(sandbox_id, status_filter).await,
431-
}
432-
}
433-
434-
/// Update the status of a draft chunk.
435-
pub async fn update_draft_chunk_status(
436-
&self,
437-
id: &str,
438-
status: &str,
439-
decided_at_ms: Option<i64>,
440-
) -> PersistenceResult<bool> {
441-
match self {
442-
Self::Postgres(store) => {
443-
store
444-
.update_draft_chunk_status(id, status, decided_at_ms)
445-
.await
446-
}
447-
Self::Sqlite(store) => {
448-
store
449-
.update_draft_chunk_status(id, status, decided_at_ms)
450-
.await
451-
}
452-
}
453-
}
454-
455-
/// Update the proposed rule on a pending draft chunk.
456-
pub async fn update_draft_chunk_rule(
457-
&self,
458-
id: &str,
459-
proposed_rule: &[u8],
460-
) -> PersistenceResult<bool> {
461-
match self {
462-
Self::Postgres(store) => store.update_draft_chunk_rule(id, proposed_rule).await,
463-
Self::Sqlite(store) => store.update_draft_chunk_rule(id, proposed_rule).await,
464-
}
465-
}
466-
467-
/// Delete all draft chunks for a sandbox with a given status.
468-
pub async fn delete_draft_chunks(
469-
&self,
470-
sandbox_id: &str,
471-
status: &str,
472-
) -> PersistenceResult<u64> {
473-
match self {
474-
Self::Postgres(store) => store.delete_draft_chunks(sandbox_id, status).await,
475-
Self::Sqlite(store) => store.delete_draft_chunks(sandbox_id, status).await,
476-
}
477-
}
478-
479-
/// Get the current maximum draft version for a sandbox.
480-
pub async fn get_draft_version(&self, sandbox_id: &str) -> PersistenceResult<i64> {
481-
match self {
482-
Self::Postgres(store) => store.get_draft_version(sandbox_id).await,
483-
Self::Sqlite(store) => store.get_draft_version(sandbox_id).await,
484-
}
485-
}
486-
}
487-
488-
pub(crate) fn policy_payload_from_record(record: &PolicyRecord) -> PersistenceResult<Vec<u8>> {
489-
let policy = ProtoSandboxPolicy::decode(record.policy_payload.as_slice())
490-
.map_err(|e| PersistenceError::Decode(format!("decode policy payload failed: {e}")))?;
491-
Ok(PolicyRevisionPayload {
492-
policy: Some(policy),
493-
hash: record.policy_hash.clone(),
494-
load_error: record.load_error.clone().unwrap_or_default(),
495-
loaded_at_ms: record.loaded_at_ms.unwrap_or(0),
496-
}
497-
.encode_to_vec())
498-
}
499-
500-
pub(crate) fn policy_record_from_parts(
501-
id: String,
502-
sandbox_id: String,
503-
version: i64,
504-
status: String,
505-
payload: &[u8],
506-
created_at_ms: i64,
507-
) -> PersistenceResult<PolicyRecord> {
508-
let wrapper = PolicyRevisionPayload::decode(payload)
509-
.map_err(|e| PersistenceError::Decode(format!("decode policy wrapper failed: {e}")))?;
510-
let policy = wrapper
511-
.policy
512-
.ok_or_else(|| PersistenceError::Decode("policy wrapper missing policy".to_string()))?;
513-
Ok(PolicyRecord {
514-
id,
515-
sandbox_id,
516-
version,
517-
policy_payload: policy.encode_to_vec(),
518-
policy_hash: wrapper.hash,
519-
status,
520-
load_error: if wrapper.load_error.is_empty() {
521-
None
522-
} else {
523-
Some(wrapper.load_error)
524-
},
525-
created_at_ms,
526-
loaded_at_ms: (wrapper.loaded_at_ms > 0).then_some(wrapper.loaded_at_ms),
527-
})
528-
}
529-
530-
pub(crate) fn draft_chunk_payload_from_record(
531-
chunk: &DraftChunkRecord,
532-
) -> PersistenceResult<Vec<u8>> {
533-
let proposed_rule = if chunk.proposed_rule.is_empty() {
534-
None
535-
} else {
536-
Some(
537-
NetworkPolicyRule::decode(chunk.proposed_rule.as_slice())
538-
.map_err(|e| PersistenceError::Decode(format!("decode draft rule failed: {e}")))?,
539-
)
540-
};
541-
Ok(DraftChunkPayload {
542-
rule_name: chunk.rule_name.clone(),
543-
proposed_rule,
544-
rationale: chunk.rationale.clone(),
545-
security_notes: chunk.security_notes.clone(),
546-
confidence: chunk.confidence as f32,
547-
decided_at_ms: chunk.decided_at_ms.unwrap_or(0),
548-
host: chunk.host.clone(),
549-
port: chunk.port,
550-
binary: chunk.binary.clone(),
551-
draft_version: chunk.draft_version,
552-
}
553-
.encode_to_vec())
554-
}
555-
556-
pub(crate) fn draft_chunk_record_from_parts(
557-
id: String,
558-
sandbox_id: String,
559-
status: String,
560-
hit_count: i64,
561-
payload: &[u8],
562-
created_at_ms: i64,
563-
updated_at_ms: i64,
564-
) -> PersistenceResult<DraftChunkRecord> {
565-
let wrapper = DraftChunkPayload::decode(payload)
566-
.map_err(|e| PersistenceError::Decode(format!("decode draft chunk wrapper failed: {e}")))?;
567-
let proposed_rule = wrapper
568-
.proposed_rule
569-
.map(|rule| rule.encode_to_vec())
570-
.unwrap_or_default();
571-
Ok(DraftChunkRecord {
572-
id,
573-
sandbox_id,
574-
draft_version: wrapper.draft_version,
575-
status,
576-
rule_name: wrapper.rule_name,
577-
proposed_rule,
578-
rationale: wrapper.rationale,
579-
security_notes: wrapper.security_notes,
580-
confidence: f64::from(wrapper.confidence),
581-
created_at_ms,
582-
decided_at_ms: (wrapper.decided_at_ms > 0).then_some(wrapper.decided_at_ms),
583-
host: wrapper.host,
584-
port: wrapper.port,
585-
binary: wrapper.binary,
586-
hit_count: i32::try_from(hit_count).unwrap_or(i32::MAX),
587-
first_seen_ms: created_at_ms,
588-
last_seen_ms: updated_at_ms,
589-
})
590283
}
591284

592285
pub fn current_time_ms() -> PersistenceResult<i64> {

crates/openshell-server/src/persistence/postgres.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use super::{
5-
DraftChunkRecord, ObjectRecord, PersistenceResult, PolicyRecord, current_time_ms,
6-
draft_chunk_payload_from_record, draft_chunk_record_from_parts, map_db_error,
7-
map_migrate_error, policy_payload_from_record, policy_record_from_parts,
5+
DraftChunkRecord, ObjectRecord, PersistenceResult, PolicyRecord, current_time_ms, map_db_error,
6+
map_migrate_error,
7+
};
8+
use crate::policy_store::{
9+
draft_chunk_payload_from_record, draft_chunk_record_from_parts, policy_payload_from_record,
10+
policy_record_from_parts,
811
};
912
use sqlx::postgres::PgPoolOptions;
1013
use sqlx::{PgPool, Row};

crates/openshell-server/src/persistence/sqlite.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use super::{
5-
DraftChunkRecord, ObjectRecord, PersistenceResult, PolicyRecord, current_time_ms,
6-
draft_chunk_payload_from_record, draft_chunk_record_from_parts, map_db_error,
7-
map_migrate_error, policy_payload_from_record, policy_record_from_parts,
5+
DraftChunkRecord, ObjectRecord, PersistenceResult, PolicyRecord, current_time_ms, map_db_error,
6+
map_migrate_error,
7+
};
8+
use crate::policy_store::{
9+
draft_chunk_payload_from_record, draft_chunk_record_from_parts, policy_payload_from_record,
10+
policy_record_from_parts,
811
};
912
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
1013
use sqlx::{Row, SqlitePool};

crates/openshell-server/src/persistence/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use super::{ObjectType, Store, generate_name};
5+
use crate::policy_store::PolicyStoreExt;
56
use openshell_core::proto::{ObjectForTest, SandboxPolicy};
67
use prost::Message;
78

0 commit comments

Comments
 (0)