Skip to content

Commit 63985d5

Browse files
committed
refactor: refactor admin config to global
Signed-off-by: allure <[email protected]>
1 parent 750e2b7 commit 63985d5

File tree

6 files changed

+99
-189
lines changed

6 files changed

+99
-189
lines changed

ceres/src/api_service/admin_ops.rs

Lines changed: 58 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
//! Path-aware admin permission operations.
1+
//! Global admin permission operations.
22
//!
3-
//! This module provides admin permission checking with path context support.
4-
//! Each root directory (e.g., project, doc, release) can have its own admin list
5-
//! defined in its `.mega_cedar.json` file.
3+
//! This module provides admin permission checking for the monorepo system.
4+
//! All admin permissions are defined in a single `.mega_cedar.json` file
5+
//! located in the root directory (`/`).
66
//!
77
//! # Design
8-
//! - Admin permissions are scoped to root directories (first-level dirs under /)
9-
//! - Each root directory has its own `.mega_cedar.json` file
10-
//! - Cache keys are namespaced by both instance prefix and root directory
11-
//! - Empty paths fall back to the default directory (project)
8+
//! - A single global admin list applies to the entire monorepo
9+
//! - The admin configuration file is stored at `/.mega_cedar.json`
10+
//! - Redis caching is used to avoid repeated file parsing
1211
1312
use common::errors::MegaError;
1413
use git_internal::internal::object::tree::Tree;
@@ -17,123 +16,82 @@ use redis::AsyncCommands;
1716

1817
use crate::api_service::mono_api_service::MonoApiService;
1918

20-
/// Cache TTL for admin lists (10 minutes).
19+
/// Cache TTL for admin list (10 minutes).
2120
pub const ADMIN_CACHE_TTL: u64 = 600;
2221

23-
/// The Cedar entity file name in each root directory.
22+
/// The Cedar entity file name in root directory.
2423
pub const ADMIN_FILE: &str = ".mega_cedar.json";
2524

26-
/// Default root directory when path is empty or root.
27-
pub const DEFAULT_ROOT_DIR: &str = "project";
28-
29-
/// Extract the root directory from a path.
30-
///
31-
/// Examples:
32-
/// - `/project/src/main.rs` -> `project`
33-
/// - `/doc/readme.md` -> `doc`
34-
/// - `/` or empty -> `project` (default)
35-
pub fn extract_root_dir(path: &str) -> String {
36-
let path = path.trim_start_matches('/');
37-
path.split('/')
38-
.next()
39-
.filter(|s| !s.is_empty())
40-
.unwrap_or(DEFAULT_ROOT_DIR)
41-
.to_string()
42-
}
25+
/// Redis cache key suffix for admin list.
26+
const ADMIN_CACHE_KEY_SUFFIX: &str = "admin:list";
4327

4428
impl MonoApiService {
45-
/// Check if a user is an admin for the specified path context.
46-
pub async fn check_is_admin(&self, username: &str, path: &str) -> Result<bool, MegaError> {
47-
let root_dir = extract_root_dir(path);
48-
let admins = self.get_effective_admins(&root_dir).await?;
29+
/// Check if a user is an admin.
30+
pub async fn check_is_admin(&self, username: &str) -> Result<bool, MegaError> {
31+
let admins = self.get_effective_admins().await?;
4932
Ok(admins.contains(&username.to_string()))
5033
}
5134

52-
/// Retrieve all admin usernames for the specified path context.
53-
pub async fn get_all_admins(&self, path: &str) -> Result<Vec<String>, MegaError> {
54-
let root_dir = extract_root_dir(path);
55-
self.get_effective_admins(&root_dir).await
35+
/// Retrieve all admin usernames.
36+
pub async fn get_all_admins(&self) -> Result<Vec<String>, MegaError> {
37+
self.get_effective_admins().await
5638
}
5739

5840
/// Get admins from cache or storage.
59-
async fn get_effective_admins(&self, root_dir: &str) -> Result<Vec<String>, MegaError> {
60-
if let Ok(admins) = self.get_admins_from_cache(root_dir).await {
41+
/// This method first attempts to read from Redis cache. On cache miss,
42+
/// it loads the admin list from the `.mega_cedar.json` file and caches
43+
/// the result.
44+
async fn get_effective_admins(&self) -> Result<Vec<String>, MegaError> {
45+
if let Ok(admins) = self.get_admins_from_cache().await {
6146
return Ok(admins);
6247
}
6348

64-
let store = self.load_admin_entity_store(root_dir).await?;
49+
let store = self.load_admin_entity_store().await?;
6550
let resolver = saturn::admin_resolver::AdminResolver::from_entity_store(&store);
6651
let admins = resolver.admin_list();
6752

68-
if let Err(e) = self.cache_admins(root_dir, &admins).await {
69-
tracing::warn!("Failed to write admin cache for {}: {}", root_dir, e);
53+
if let Err(e) = self.cache_admins(&admins).await {
54+
tracing::warn!("Failed to write admin cache: {}", e);
7055
}
7156

7257
Ok(admins)
7358
}
7459

75-
/// Invalidate the admin list cache for a root directory.
76-
pub async fn invalidate_admin_cache(&self, root_dir: &str) {
60+
/// Invalidate the admin list cache.
61+
/// This should be called when the `.mega_cedar.json` file is modified.
62+
pub async fn invalidate_admin_cache(&self) {
7763
let mut conn = self.git_object_cache.connection.clone();
78-
let key = format!("{}:admin:list:{}", self.git_object_cache.prefix, root_dir);
64+
let key = format!(
65+
"{}:{}",
66+
self.git_object_cache.prefix, ADMIN_CACHE_KEY_SUFFIX
67+
);
7968
if let Err(e) = conn.del::<_, ()>(&key).await {
80-
tracing::warn!("Failed to invalidate admin cache for {}: {}", root_dir, e);
69+
tracing::warn!("Failed to invalidate admin cache: {}", e);
8170
}
8271
}
8372

84-
/// Load EntityStore from `/{root_dir}/.mega_cedar.json`.
85-
async fn load_admin_entity_store(
86-
&self,
87-
root_dir: &str,
88-
) -> Result<saturn::entitystore::EntityStore, MegaError> {
89-
let dir_path = format!("/{}", root_dir);
73+
/// Load EntityStore from `/.mega_cedar.json`.
74+
async fn load_admin_entity_store(&self) -> Result<saturn::entitystore::EntityStore, MegaError> {
9075
let mono_storage = self.storage.mono_storage();
9176

92-
let target_tree = if let Ok(Some(dir_ref)) = mono_storage.get_main_ref(&dir_path).await {
93-
Tree::from_mega_model(
94-
mono_storage
95-
.get_tree_by_hash(&dir_ref.ref_tree_hash)
96-
.await?
97-
.ok_or_else(|| {
98-
MegaError::Other(format!("Tree {} not found", dir_ref.ref_tree_hash))
99-
})?,
100-
)
101-
} else {
102-
// Fallback: traverse from root to find the directory
103-
let root_ref = mono_storage
104-
.get_main_ref("/")
77+
let root_ref = mono_storage
78+
.get_main_ref("/")
79+
.await?
80+
.ok_or_else(|| MegaError::Other("Root ref not found".into()))?;
81+
82+
let root_tree = Tree::from_mega_model(
83+
mono_storage
84+
.get_tree_by_hash(&root_ref.ref_tree_hash)
10585
.await?
106-
.ok_or_else(|| MegaError::Other("Root ref not found".into()))?;
107-
108-
let root_tree = Tree::from_mega_model(
109-
mono_storage
110-
.get_tree_by_hash(&root_ref.ref_tree_hash)
111-
.await?
112-
.ok_or_else(|| MegaError::Other("Root tree not found".into()))?,
113-
);
114-
115-
let dir_item = root_tree
116-
.tree_items
117-
.iter()
118-
.find(|item| item.name == root_dir)
119-
.ok_or_else(|| MegaError::Other(format!("'{}' directory not found", root_dir)))?;
120-
121-
Tree::from_mega_model(
122-
mono_storage
123-
.get_tree_by_hash(&dir_item.id.to_string())
124-
.await?
125-
.ok_or_else(|| {
126-
MegaError::Other(format!("Tree for '{}' not found", root_dir))
127-
})?,
128-
)
129-
};
130-
131-
let blob_item = target_tree
86+
.ok_or_else(|| MegaError::Other("Root tree not found".into()))?,
87+
);
88+
89+
let blob_item = root_tree
13290
.tree_items
13391
.iter()
13492
.find(|item| item.name == ADMIN_FILE)
13593
.ok_or_else(|| {
136-
MegaError::Other(format!("{} not found in /{}", ADMIN_FILE, root_dir))
94+
MegaError::Other(format!("{} not found in root directory", ADMIN_FILE))
13795
})?;
13896

13997
let content_bytes = self
@@ -149,9 +107,12 @@ impl MonoApiService {
149107
.map_err(|e| MegaError::Other(format!("JSON parse failed: {}", e)))
150108
}
151109

152-
async fn get_admins_from_cache(&self, root_dir: &str) -> Result<Vec<String>, MegaError> {
110+
async fn get_admins_from_cache(&self) -> Result<Vec<String>, MegaError> {
153111
let mut conn = self.git_object_cache.connection.clone();
154-
let key = format!("{}:admin:list:{}", self.git_object_cache.prefix, root_dir);
112+
let key = format!(
113+
"{}:{}",
114+
self.git_object_cache.prefix, ADMIN_CACHE_KEY_SUFFIX
115+
);
155116
let data: Option<String> = conn.get(&key).await?;
156117

157118
match data {
@@ -161,12 +122,15 @@ impl MonoApiService {
161122
}
162123
}
163124

164-
async fn cache_admins(&self, root_dir: &str, admins: &[String]) -> Result<(), MegaError> {
125+
async fn cache_admins(&self, admins: &[String]) -> Result<(), MegaError> {
165126
let mut conn = self.git_object_cache.connection.clone();
166127
let json = serde_json::to_string(admins)
167128
.map_err(|e| MegaError::Other(format!("Serialize failed: {}", e)))?;
168129

169-
let key = format!("{}:admin:list:{}", self.git_object_cache.prefix, root_dir);
130+
let key = format!(
131+
"{}:{}",
132+
self.git_object_cache.prefix, ADMIN_CACHE_KEY_SUFFIX
133+
);
170134
conn.set_ex::<_, _, ()>(&key, json, ADMIN_CACHE_TTL).await?;
171135
Ok(())
172136
}

ceres/src/api_service/mono_api_service.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,18 +1242,13 @@ impl MonoApiService {
12421242
.map_err(|e| GitError::CustomError(format!("Failed to update CL status: {}", e)))?;
12431243

12441244
// Invalidate admin cache when .mega_cedar.json is modified.
1245-
// File paths from get_sorted_changed_file_list are relative to cl.path.
12461245
if let Ok(files) = self.get_sorted_changed_file_list(&cl.link, None).await {
1247-
for file in &files {
1248-
let normalized_path = file.replace('\\', "/");
1249-
if normalized_path.ends_with(crate::api_service::admin_ops::ADMIN_FILE) {
1250-
let root_dir = if cl.path == "/" {
1251-
crate::api_service::admin_ops::extract_root_dir(&normalized_path)
1252-
} else {
1253-
crate::api_service::admin_ops::extract_root_dir(&cl.path)
1254-
};
1255-
self.invalidate_admin_cache(&root_dir).await;
1256-
}
1246+
let admin_file_modified = files.iter().any(|file| {
1247+
let normalized = file.replace('\\', "/");
1248+
normalized.ends_with(crate::api_service::admin_ops::ADMIN_FILE)
1249+
});
1250+
if admin_file_modified {
1251+
self.invalidate_admin_cache().await;
12571252
}
12581253
}
12591254

jupiter/src/utils/converter.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -417,26 +417,37 @@ pub fn init_trees(
417417
let mut root_items = Vec::new();
418418
let mut trees = Vec::new();
419419
let mut blobs = Vec::new();
420+
421+
// Create unique .gitkeep for each root directory to ensure different tree hashes
420422
for dir in mono_config.root_dirs.clone() {
421-
let entity_str =
422-
saturn::entitystore::generate_entity(&mono_config.admin, &format!("/{dir}")).unwrap();
423-
let blob = Blob::from_content(&entity_str);
423+
let gitkeep_content = format!("Placeholder file for /{} directory", dir);
424+
let gitkeep_blob = Blob::from_content(&gitkeep_content);
425+
blobs.push(gitkeep_blob.clone());
424426

425427
let tree_item = TreeItem {
426428
mode: TreeItemMode::Blob,
427-
id: blob.id,
428-
name: String::from(".mega_cedar.json"),
429+
id: gitkeep_blob.id,
430+
name: String::from(".gitkeep"),
429431
};
430-
let tree = Tree::from_tree_items(vec![tree_item.clone()]).unwrap();
432+
let tree = Tree::from_tree_items(vec![tree_item]).unwrap();
431433
root_items.push(TreeItem {
432434
mode: TreeItemMode::Tree,
433435
id: tree.id,
434436
name: dir,
435437
});
436438
trees.push(tree);
437-
blobs.push(blob);
438439
}
439440

441+
// Create global .mega_cedar.json in root directory
442+
let entity_str = saturn::entitystore::generate_entity(&mono_config.admin, "/").unwrap();
443+
let cedar_blob = Blob::from_content(&entity_str);
444+
root_items.push(TreeItem {
445+
mode: TreeItemMode::Blob,
446+
id: cedar_blob.id,
447+
name: String::from(".mega_cedar.json"),
448+
});
449+
blobs.push(cedar_blob);
450+
440451
inject_root_buck_files(&mut root_items, &mut blobs, &mono_config.root_dirs);
441452

442453
// Ensure the `toolchains` cell has a BUCK file at repo initialization time.
@@ -884,6 +895,7 @@ mod test {
884895
let mega_blobs = converter.mega_blobs.borrow().clone();
885896
let dir_nums = mono_config.root_dirs.len();
886897
assert_eq!(mega_trees.len(), dir_nums + 1);
898+
// Blobs: dir_nums (.gitkeep per dir) + 1 (.mega_cedar.json) + 2 (.buckconfig + .buckroot)
887899
assert_eq!(mega_blobs.len(), dir_nums + 3);
888900
}
889901

0 commit comments

Comments
 (0)