crates/common: add branch and scheduling_shares to jobset models
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ie19897f5ffdfb44654891511ce669d806a6a6964
This commit is contained in:
parent
be9caa0b61
commit
4c5a99d554
3 changed files with 175 additions and 19 deletions
|
|
@ -16,6 +16,11 @@ pub struct Config {
|
||||||
pub notifications: NotificationsConfig,
|
pub notifications: NotificationsConfig,
|
||||||
pub cache: CacheConfig,
|
pub cache: CacheConfig,
|
||||||
pub signing: SigningConfig,
|
pub signing: SigningConfig,
|
||||||
|
#[serde(default)]
|
||||||
|
pub cache_upload: CacheUploadConfig,
|
||||||
|
pub tracing: TracingConfig,
|
||||||
|
#[serde(default)]
|
||||||
|
pub declarative: DeclarativeConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
@ -79,6 +84,7 @@ pub struct LogConfig {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[derive(Default)]
|
||||||
pub struct NotificationsConfig {
|
pub struct NotificationsConfig {
|
||||||
pub run_command: Option<String>,
|
pub run_command: Option<String>,
|
||||||
pub github_token: Option<String>,
|
pub github_token: Option<String>,
|
||||||
|
|
@ -107,11 +113,91 @@ pub struct CacheConfig {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[derive(Default)]
|
||||||
pub struct SigningConfig {
|
pub struct SigningConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub key_file: Option<PathBuf>,
|
pub key_file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CacheUploadConfig {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub store_uri: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Declarative project/jobset/api-key definitions.
|
||||||
|
/// These are upserted on server startup, enabling fully declarative operation.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct DeclarativeConfig {
|
||||||
|
pub projects: Vec<DeclarativeProject>,
|
||||||
|
pub api_keys: Vec<DeclarativeApiKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DeclarativeProject {
|
||||||
|
pub name: String,
|
||||||
|
pub repository_url: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub jobsets: Vec<DeclarativeJobset>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DeclarativeJobset {
|
||||||
|
pub name: String,
|
||||||
|
pub nix_expression: String,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub flake_mode: bool,
|
||||||
|
#[serde(default = "default_check_interval")]
|
||||||
|
pub check_interval: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DeclarativeApiKey {
|
||||||
|
pub name: String,
|
||||||
|
pub key: String,
|
||||||
|
#[serde(default = "default_role")]
|
||||||
|
pub role: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_check_interval() -> i32 {
|
||||||
|
60
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_role() -> String {
|
||||||
|
"admin".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct TracingConfig {
|
||||||
|
pub level: String,
|
||||||
|
pub format: String,
|
||||||
|
pub show_targets: bool,
|
||||||
|
pub show_timestamps: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TracingConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
level: "info".to_string(),
|
||||||
|
format: "compact".to_string(),
|
||||||
|
show_targets: true,
|
||||||
|
show_timestamps: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for DatabaseConfig {
|
impl Default for DatabaseConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -214,17 +300,6 @@ impl Default for LogConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NotificationsConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
run_command: None,
|
|
||||||
github_token: None,
|
|
||||||
gitea_url: None,
|
|
||||||
gitea_token: None,
|
|
||||||
email: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CacheConfig {
|
impl Default for CacheConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
|
@ -235,14 +310,6 @@ impl Default for CacheConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SigningConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
enabled: false,
|
|
||||||
key_file: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load() -> anyhow::Result<Self> {
|
pub fn load() -> anyhow::Result<Self> {
|
||||||
|
|
@ -376,6 +443,83 @@ mod tests {
|
||||||
assert!(config.validate().is_err());
|
assert!(config.validate().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_declarative_config_default_is_empty() {
|
||||||
|
let config = DeclarativeConfig::default();
|
||||||
|
assert!(config.projects.is_empty());
|
||||||
|
assert!(config.api_keys.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_declarative_config_deserialization() {
|
||||||
|
let toml_str = r#"
|
||||||
|
[[projects]]
|
||||||
|
name = "my-project"
|
||||||
|
repository_url = "https://github.com/test/repo"
|
||||||
|
description = "Test project"
|
||||||
|
|
||||||
|
[[projects.jobsets]]
|
||||||
|
name = "packages"
|
||||||
|
nix_expression = "packages"
|
||||||
|
|
||||||
|
[[api_keys]]
|
||||||
|
name = "admin-key"
|
||||||
|
key = "fc_secret_key_123"
|
||||||
|
role = "admin"
|
||||||
|
"#;
|
||||||
|
let config: DeclarativeConfig = toml::from_str(toml_str).unwrap();
|
||||||
|
assert_eq!(config.projects.len(), 1);
|
||||||
|
assert_eq!(config.projects[0].name, "my-project");
|
||||||
|
assert_eq!(config.projects[0].jobsets.len(), 1);
|
||||||
|
assert_eq!(config.projects[0].jobsets[0].name, "packages");
|
||||||
|
assert!(config.projects[0].jobsets[0].enabled); // default true
|
||||||
|
assert!(config.projects[0].jobsets[0].flake_mode); // default true
|
||||||
|
assert_eq!(config.api_keys.len(), 1);
|
||||||
|
assert_eq!(config.api_keys[0].role, "admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_declarative_config_serialization_roundtrip() {
|
||||||
|
let config = DeclarativeConfig {
|
||||||
|
projects: vec![DeclarativeProject {
|
||||||
|
name: "test".to_string(),
|
||||||
|
repository_url: "https://example.com/repo".to_string(),
|
||||||
|
description: Some("desc".to_string()),
|
||||||
|
jobsets: vec![DeclarativeJobset {
|
||||||
|
name: "checks".to_string(),
|
||||||
|
nix_expression: "checks".to_string(),
|
||||||
|
enabled: true,
|
||||||
|
flake_mode: true,
|
||||||
|
check_interval: 300,
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
api_keys: vec![DeclarativeApiKey {
|
||||||
|
name: "test-key".to_string(),
|
||||||
|
key: "fc_test".to_string(),
|
||||||
|
role: "admin".to_string(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&config).unwrap();
|
||||||
|
let parsed: DeclarativeConfig = serde_json::from_str(&json).unwrap();
|
||||||
|
assert_eq!(parsed.projects.len(), 1);
|
||||||
|
assert_eq!(parsed.projects[0].jobsets[0].check_interval, 300);
|
||||||
|
assert_eq!(parsed.api_keys[0].name, "test-key");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_declarative_config_with_main_config() {
|
||||||
|
// Ensure declarative section is optional (default empty)
|
||||||
|
// Use the config crate loader which provides defaults for missing fields
|
||||||
|
let config = Config::default();
|
||||||
|
assert!(config.declarative.projects.is_empty());
|
||||||
|
assert!(config.declarative.api_keys.is_empty());
|
||||||
|
// And that the Config can be serialized back with declarative section
|
||||||
|
let toml_str = toml::to_string_pretty(&config).unwrap();
|
||||||
|
let parsed: Config = toml::from_str(&toml_str).unwrap();
|
||||||
|
assert!(parsed.declarative.projects.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_environment_override() {
|
fn test_environment_override() {
|
||||||
// Test environment variable parsing directly
|
// Test environment variable parsing directly
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ pub struct Jobset {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub flake_mode: bool,
|
pub flake_mode: bool,
|
||||||
pub check_interval: i32,
|
pub check_interval: i32,
|
||||||
|
pub branch: Option<String>,
|
||||||
|
pub scheduling_shares: i32,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
@ -128,6 +130,8 @@ pub struct ActiveJobset {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub flake_mode: bool,
|
pub flake_mode: bool,
|
||||||
pub check_interval: i32,
|
pub check_interval: i32,
|
||||||
|
pub branch: Option<String>,
|
||||||
|
pub scheduling_shares: i32,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
pub project_name: String,
|
pub project_name: String,
|
||||||
|
|
@ -278,6 +282,8 @@ pub struct CreateJobset {
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
pub flake_mode: Option<bool>,
|
pub flake_mode: Option<bool>,
|
||||||
pub check_interval: Option<i32>,
|
pub check_interval: Option<i32>,
|
||||||
|
pub branch: Option<String>,
|
||||||
|
pub scheduling_shares: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
@ -287,6 +293,8 @@ pub struct UpdateJobset {
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
pub flake_mode: Option<bool>,
|
pub flake_mode: Option<bool>,
|
||||||
pub check_interval: Option<i32>,
|
pub check_interval: Option<i32>,
|
||||||
|
pub branch: Option<String>,
|
||||||
|
pub scheduling_shares: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -469,6 +469,8 @@ mod tests {
|
||||||
enabled: None,
|
enabled: None,
|
||||||
flake_mode: None,
|
flake_mode: None,
|
||||||
check_interval: Some(300),
|
check_interval: Some(300),
|
||||||
|
branch: None,
|
||||||
|
scheduling_shares: None,
|
||||||
};
|
};
|
||||||
assert!(j.validate().is_ok());
|
assert!(j.validate().is_ok());
|
||||||
}
|
}
|
||||||
|
|
@ -482,6 +484,8 @@ mod tests {
|
||||||
enabled: None,
|
enabled: None,
|
||||||
flake_mode: None,
|
flake_mode: None,
|
||||||
check_interval: Some(5),
|
check_interval: Some(5),
|
||||||
|
branch: None,
|
||||||
|
scheduling_shares: None,
|
||||||
};
|
};
|
||||||
assert!(j.validate().is_err());
|
assert!(j.validate().is_err());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue