1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use super::ai::AiConfig;
6use crate::backup::BackupConfig;
7
8#[derive(Debug, Clone, Serialize, Deserialize, Default)]
10pub struct ClusterConfig {
11 pub cluster: ClusterMeta,
12 #[serde(default)]
13 pub node: Vec<NodeConfig>,
14 #[serde(default)]
15 pub observability: Option<ObservabilityConfig>,
16 #[serde(default)]
17 pub ai: Option<AiConfig>,
18 #[serde(default)]
19 pub backup: Option<BackupConfig>,
20 #[serde(default)]
23 pub api_tokens: Vec<String>,
24 #[serde(default)]
26 pub token: Vec<ApiToken>,
27 #[serde(default)]
29 pub network: Option<NetworkConfig>,
30 #[serde(default)]
32 pub fallback: Option<FallbackConfig>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, Default)]
38pub struct FallbackConfig {
39 pub http: Option<String>,
41 pub tls: Option<String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct NetworkConfig {
48 #[serde(default = "default_network_provider")]
50 pub provider: String,
51 pub setup_key: Option<String>,
53 pub management_url: Option<String>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ApiToken {
60 pub name: String,
62 pub value: String,
64 #[serde(default = "default_role")]
66 pub role: Role,
67}
68
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "lowercase")]
72pub enum Role {
73 #[default]
75 Admin,
76 Deployer,
78 Viewer,
80}
81
82impl Role {
83 pub fn can(self, action: &str) -> bool {
88 match self {
89 Role::Admin => true,
90 Role::Deployer => matches!(
91 action,
92 "deploy" | "stop" | "scale" | "rollback" | "status" | "logs" | "cluster_info"
93 ),
94 Role::Viewer => matches!(action, "status" | "logs" | "cluster_info"),
95 }
96 }
97}
98
99fn default_role() -> Role {
100 Role::Admin
101}
102
103fn default_network_provider() -> String {
104 "netbird".into()
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ClusterMeta {
109 #[serde(default = "default_cluster_name")]
110 pub name: String,
111 pub domain: Option<String>,
112 pub acme_email: Option<String>,
113 #[serde(default = "default_log_level")]
114 pub log_level: String,
115 #[serde(default = "default_api_port")]
116 pub api_port: u16,
117 #[serde(default = "default_grpc_port")]
118 pub grpc_port: u16,
119}
120
121impl Default for ClusterMeta {
122 fn default() -> Self {
123 Self {
124 name: default_cluster_name(),
125 domain: None,
126 acme_email: None,
127 log_level: default_log_level(),
128 api_port: default_api_port(),
129 grpc_port: default_grpc_port(),
130 }
131 }
132}
133
134fn default_cluster_name() -> String {
135 "orca".into()
136}
137
138pub(crate) fn default_log_level() -> String {
139 "info".into()
140}
141
142pub(crate) fn default_api_port() -> u16 {
143 6880
144}
145
146pub(crate) fn default_grpc_port() -> u16 {
147 6881
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct NodeConfig {
152 pub address: String,
153 #[serde(default)]
154 pub labels: HashMap<String, String>,
155 #[serde(default)]
157 pub gpus: Vec<NodeGpuConfig>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct NodeGpuConfig {
162 pub vendor: String,
164 #[serde(default = "default_gpu_count")]
166 pub count: u32,
167 pub model: Option<String>,
169}
170
171pub(crate) fn default_gpu_count() -> u32 {
172 1
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct ObservabilityConfig {
177 pub otlp_endpoint: Option<String>,
178 pub alerts: Option<AlertChannelConfig>,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct AlertChannelConfig {
183 pub webhook: Option<String>,
184 pub email: Option<String>,
185}